aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Elliott <steell@google.com>2019-09-06 14:30:11 -0700
committerandroid-build-merger <android-build-merger@google.com>2019-09-06 14:30:11 -0700
commit942c05c9b2da9b2c487fd8a916444029ff5a3c44 (patch)
tree913334bc94b3efe522b72a1bdc6a7df2bfb94007
parente347054e79c6800142265bb1b6446c41e7f3ecc0 (diff)
parent83931ab33cdde668d0fe491939186d751431709c (diff)
downloadplatform_external_kotlinx.coroutines-942c05c9b2da9b2c487fd8a916444029ff5a3c44.tar.gz
platform_external_kotlinx.coroutines-942c05c9b2da9b2c487fd8a916444029ff5a3c44.tar.bz2
platform_external_kotlinx.coroutines-942c05c9b2da9b2c487fd8a916444029ff5a3c44.zip
Merge remote-tracking branch 'aosp/upstream-master' into master am: 555408c0d3 am: 102c6c0166
am: 83931ab33c Change-Id: I0b64e3c67506471124872e2f20dcad6db7e7be8d
-rw-r--r--.gitignore8
-rw-r--r--CHANGES.md951
-rw-r--r--CODE_OF_CONDUCT.md4
-rw-r--r--LICENSE.txt13
-rw-r--r--README.md291
-rw-r--r--RELEASE.md80
-rw-r--r--benchmarks/build.gradle77
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java163
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java174
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java149
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java327
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java82
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/CancellableContinuationBenchmark.kt53
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt146
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt53
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/ForkJoinBenchmark.kt156
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/LaunchBenchmark.kt55
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt47
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt97
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/StatefulAwaitsBenchmark.kt119
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/actors/ConcurrentStatefulActorBenchmark.kt139
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/actors/CycledActorsBenchmark.kt122
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongActorBenchmark.kt103
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongAkkaBenchmark.kt121
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongWithBlockingContext.kt65
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/actors/StatefulActorAkkaBenchmark.kt174
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/actors/StatefulActorBenchmark.kt118
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt47
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt123
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt39
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt134
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt195
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt12
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/README.md42
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt146
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt104
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt103
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt85
-rw-r--r--benchmarks/src/jmh/resources/ospd.txt.gzbin0 -> 197126 bytes
-rw-r--r--benchmarks/src/jmh/resources/words.shakespeare.txt.gzbin0 -> 81824 bytes
-rw-r--r--binary-compatibility-validator/README.md10
-rw-r--r--binary-compatibility-validator/build.gradle58
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-android.txt13
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt1168
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt50
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-guava.txt8
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-javafx.txt12
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-jdk8.txt16
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-play-services.txt6
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt69
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt58
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt82
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-slf4j.txt19
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-swing.txt11
-rw-r--r--binary-compatibility-validator/reference-public-api/kotlinx-coroutines-test.txt67
-rw-r--r--binary-compatibility-validator/resources/api.properties9
-rw-r--r--binary-compatibility-validator/src/PublicApiDump.kt120
-rw-r--r--binary-compatibility-validator/src/asmUtils.kt173
-rw-r--r--binary-compatibility-validator/src/kotlinVisibilities.kt57
-rw-r--r--binary-compatibility-validator/test/CasesPublicAPITest.kt98
-rw-r--r--binary-compatibility-validator/test/PublicApiTest.kt96
-rw-r--r--binary-compatibility-validator/test/cases/companions/companions.kt106
-rw-r--r--binary-compatibility-validator/test/cases/companions/companions.txt66
-rw-r--r--binary-compatibility-validator/test/cases/inline/inline.txt9
-rw-r--r--binary-compatibility-validator/test/cases/inline/inlineExposed.kt28
-rw-r--r--binary-compatibility-validator/test/cases/inline/inlineOnly.kt9
-rw-r--r--binary-compatibility-validator/test/cases/interfaces/interfaceWithEmptyImpls.kt10
-rw-r--r--binary-compatibility-validator/test/cases/interfaces/interfaceWithImpls.kt18
-rw-r--r--binary-compatibility-validator/test/cases/interfaces/interfaces.txt27
-rw-r--r--binary-compatibility-validator/test/cases/internal/internal.txt3
-rw-r--r--binary-compatibility-validator/test/cases/internal/internalClass.kt11
-rw-r--r--binary-compatibility-validator/test/cases/internal/internalMultifile1.kt9
-rw-r--r--binary-compatibility-validator/test/cases/internal/internalMultifile2.kt9
-rw-r--r--binary-compatibility-validator/test/cases/internal/internalPart.kt11
-rw-r--r--binary-compatibility-validator/test/cases/internal/publicClassInternalMember.kt13
-rw-r--r--binary-compatibility-validator/test/cases/java/Facade.java16
-rw-r--r--binary-compatibility-validator/test/cases/java/java.txt6
-rw-r--r--binary-compatibility-validator/test/cases/localClasses/fromStdlib.kt7
-rw-r--r--binary-compatibility-validator/test/cases/localClasses/lambdas.kt24
-rw-r--r--binary-compatibility-validator/test/cases/localClasses/localClasses.kt27
-rw-r--r--binary-compatibility-validator/test/cases/localClasses/localClasses.txt18
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/internalClass.kt28
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/internalInterface.kt18
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/internalObject.kt21
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/nestedClasses.txt86
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/privateClass.kt28
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/privateInterface.kt18
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/privateObject.kt21
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/publicAbstractClass.kt16
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/publicClass.kt28
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/publicInterface.kt18
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/publicObject.kt21
-rw-r--r--binary-compatibility-validator/test/cases/nestedClasses/publicOpenClass.kt16
-rw-r--r--binary-compatibility-validator/test/cases/private/private.txt0
-rw-r--r--binary-compatibility-validator/test/cases/private/privateClassMembers.kt11
-rw-r--r--binary-compatibility-validator/test/cases/private/privateMultifile1.kt12
-rw-r--r--binary-compatibility-validator/test/cases/private/privateMultifile2.kt23
-rw-r--r--binary-compatibility-validator/test/cases/private/privatePart.kt28
-rw-r--r--binary-compatibility-validator/test/cases/protected/protected.txt19
-rw-r--r--binary-compatibility-validator/test/cases/protected/protectedInAbstract.kt12
-rw-r--r--binary-compatibility-validator/test/cases/protected/protectedInFinal.kt12
-rw-r--r--binary-compatibility-validator/test/cases/protected/protectedInOpen.kt12
-rw-r--r--binary-compatibility-validator/test/cases/public/public.txt9
-rw-r--r--binary-compatibility-validator/test/cases/public/publicMultifile1.kt9
-rw-r--r--binary-compatibility-validator/test/cases/public/publicMultifile2.kt9
-rw-r--r--binary-compatibility-validator/test/cases/public/publicPart.kt9
-rw-r--r--binary-compatibility-validator/test/cases/special/hidden.kt29
-rw-r--r--binary-compatibility-validator/test/cases/special/internalLateinitMember.kt14
-rw-r--r--binary-compatibility-validator/test/cases/special/jvmField.kt55
-rw-r--r--binary-compatibility-validator/test/cases/special/jvmNames.kt26
-rw-r--r--binary-compatibility-validator/test/cases/special/jvmOverloads.kt23
-rw-r--r--binary-compatibility-validator/test/cases/special/special.txt61
-rw-r--r--binary-compatibility-validator/test/cases/whenMappings/enumWhen.kt24
-rw-r--r--binary-compatibility-validator/test/cases/whenMappings/sealedClassWhen.kt24
-rw-r--r--binary-compatibility-validator/test/cases/whenMappings/whenMappings.txt33
-rw-r--r--binary-compatibility-validator/test/utils.kt48
-rw-r--r--build.gradle247
-rwxr-xr-xbump-version.sh36
-rw-r--r--coroutines-guide.md120
-rw-r--r--docs/_nav.yml30
-rw-r--r--docs/basics.md416
-rw-r--r--docs/cancellation-and-timeouts.md388
-rw-r--r--docs/channels.md715
-rw-r--r--docs/compatibility.md131
-rw-r--r--docs/composing-suspending-functions.md451
-rw-r--r--docs/coroutine-context-and-dispatchers.md712
-rw-r--r--docs/coroutines-guide.md33
-rw-r--r--docs/debugging.md97
-rw-r--r--docs/exception-handling.md531
-rw-r--r--docs/flow.md1855
-rw-r--r--docs/images/after.pngbin0 -> 297058 bytes
-rw-r--r--docs/images/before.pngbin0 -> 161429 bytes
-rw-r--r--docs/select-expression.md568
-rw-r--r--docs/shared-mutable-state-and-concurrency.md554
-rw-r--r--gradle.properties33
-rw-r--r--gradle/compile-common.gradle14
-rw-r--r--gradle/compile-js-multiplatform.gradle61
-rw-r--r--gradle/compile-js.gradle28
-rw-r--r--gradle/compile-jvm-multiplatform.gradle39
-rw-r--r--gradle/compile-jvm.gradle33
-rw-r--r--gradle/compile-native-multiplatform.gradle30
-rw-r--r--gradle/dokka.gradle85
-rw-r--r--gradle/experimental.gradle13
-rw-r--r--gradle/maven-central.gradle37
-rw-r--r--gradle/node-js.gradle40
-rw-r--r--gradle/publish-bintray.gradle109
-rw-r--r--gradle/publish-npm-js.gradle52
-rw-r--r--gradle/targets.gradle28
-rw-r--r--gradle/test-mocha-js.gradle93
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 56172 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties9
-rwxr-xr-xgradlew172
-rw-r--r--gradlew.bat84
-rw-r--r--integration/README.md27
-rw-r--r--integration/kotlinx-coroutines-guava/README.md60
-rw-r--r--integration/kotlinx-coroutines-guava/build.gradle16
-rw-r--r--integration/kotlinx-coroutines-guava/package.list16
-rw-r--r--integration/kotlinx-coroutines-guava/src/ListenableFuture.kt468
-rw-r--r--integration/kotlinx-coroutines-guava/test/ListenableFutureExceptionsTest.kt91
-rw-r--r--integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt565
-rw-r--r--integration/kotlinx-coroutines-jdk8/README.md64
-rw-r--r--integration/kotlinx-coroutines-jdk8/build.gradle4
-rw-r--r--integration/kotlinx-coroutines-jdk8/src/future/Future.kt184
-rw-r--r--integration/kotlinx-coroutines-jdk8/src/time/Time.kt57
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/examples/CancelFuture-example.kt31
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/examples/ExplicitJob-example.kt36
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/examples/ToFuture-example.kt23
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/examples/Try.kt26
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/examples/simple-example-1.kt22
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/examples/simple-example-2.kt22
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/examples/simple-example-3.kt26
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/examples/withTimeout-example.kt38
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/future/AsFutureTest.kt124
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/future/FutureExceptionsTest.kt87
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt493
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt79
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/time/WithTimeoutTest.kt68
-rw-r--r--integration/kotlinx-coroutines-play-services/README.md29
-rw-r--r--integration/kotlinx-coroutines-play-services/build.gradle84
-rw-r--r--integration/kotlinx-coroutines-play-services/package.list1
-rw-r--r--integration/kotlinx-coroutines-play-services/src/Tasks.kt107
-rw-r--r--integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt18
-rw-r--r--integration/kotlinx-coroutines-play-services/test/TaskTest.kt153
-rw-r--r--integration/kotlinx-coroutines-slf4j/README.md24
-rw-r--r--integration/kotlinx-coroutines-slf4j/build.gradle13
-rw-r--r--integration/kotlinx-coroutines-slf4j/package.list21
-rw-r--r--integration/kotlinx-coroutines-slf4j/src/MDCContext.kt72
-rw-r--r--integration/kotlinx-coroutines-slf4j/test-resources/logback-test.xml15
-rw-r--r--integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt110
-rw-r--r--js/README.md4
-rw-r--r--js/example-frontend-js/README.md18
-rw-r--r--js/example-frontend-js/build.gradle36
-rw-r--r--js/example-frontend-js/npm/package.json22
-rw-r--r--js/example-frontend-js/npm/webpack.config.js53
-rw-r--r--js/example-frontend-js/src/ExampleMain.kt220
-rw-r--r--js/example-frontend-js/src/main/web/main.js8
-rw-r--r--js/example-frontend-js/src/main/web/style.css19
-rw-r--r--js/js-stub/README.md1
-rw-r--r--js/js-stub/build.gradle9
-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/README.md13
-rw-r--r--knit/build.gradle26
-rw-r--r--knit/resources/knit.properties9
-rw-r--r--knit/src/Knit.kt598
-rw-r--r--kotlinx-coroutines-bom/build.gradle24
-rw-r--r--kotlinx-coroutines-core/README.md140
-rw-r--r--kotlinx-coroutines-core/build.gradle115
-rw-r--r--kotlinx-coroutines-core/common/README.md154
-rw-r--r--kotlinx-coroutines-core/common/src/AbstractCoroutine.kt156
-rw-r--r--kotlinx-coroutines-core/common/src/Annotations.kt56
-rw-r--r--kotlinx-coroutines-core/common/src/Await.kt116
-rw-r--r--kotlinx-coroutines-core/common/src/Builders.common.kt259
-rw-r--r--kotlinx-coroutines-core/common/src/CancellableContinuation.kt270
-rw-r--r--kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt390
-rw-r--r--kotlinx-coroutines-core/common/src/CompletableDeferred.kt80
-rw-r--r--kotlinx-coroutines-core/common/src/CompletableJob.kt36
-rw-r--r--kotlinx-coroutines-core/common/src/CompletedExceptionally.kt44
-rw-r--r--kotlinx-coroutines-core/common/src/CompletionHandler.common.kt47
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineContext.common.kt23
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt121
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt80
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineName.kt29
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineScope.kt226
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineStart.kt122
-rw-r--r--kotlinx-coroutines-core/common/src/Debug.common.kt10
-rw-r--r--kotlinx-coroutines-core/common/src/Deferred.kt80
-rw-r--r--kotlinx-coroutines-core/common/src/Delay.kt79
-rw-r--r--kotlinx-coroutines-core/common/src/Dispatched.kt327
-rw-r--r--kotlinx-coroutines-core/common/src/Dispatchers.common.kt76
-rw-r--r--kotlinx-coroutines-core/common/src/EventLoop.common.kt522
-rw-r--r--kotlinx-coroutines-core/common/src/Exceptions.common.kt30
-rw-r--r--kotlinx-coroutines-core/common/src/Job.kt639
-rw-r--r--kotlinx-coroutines-core/common/src/JobSupport.kt1423
-rw-r--r--kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt45
-rw-r--r--kotlinx-coroutines-core/common/src/NonCancellable.kt123
-rw-r--r--kotlinx-coroutines-core/common/src/ResumeMode.kt61
-rw-r--r--kotlinx-coroutines-core/common/src/Runnable.common.kt21
-rw-r--r--kotlinx-coroutines-core/common/src/SchedulerTask.common.kt15
-rw-r--r--kotlinx-coroutines-core/common/src/Supervisor.kt65
-rw-r--r--kotlinx-coroutines-core/common/src/Timeout.kt128
-rw-r--r--kotlinx-coroutines-core/common/src/Unconfined.kt16
-rw-r--r--kotlinx-coroutines-core/common/src/Yield.kt32
-rw-r--r--kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt1071
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt367
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt261
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Broadcast.kt142
-rw-r--r--kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt72
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Channel.kt587
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt42
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Channels.common.kt2195
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt294
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt93
-rw-r--r--kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt61
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Produce.kt143
-rw-r--r--kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt21
-rw-r--r--kotlinx-coroutines-core/common/src/flow/Builders.kt308
-rw-r--r--kotlinx-coroutines-core/common/src/flow/Channels.kt180
-rw-r--r--kotlinx-coroutines-core/common/src/flow/Flow.kt219
-rw-r--r--kotlinx-coroutines-core/common/src/flow/FlowCollector.kt21
-rw-r--r--kotlinx-coroutines-core/common/src/flow/Migration.kt440
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt170
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/Combine.kt142
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt85
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt27
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/Merge.kt77
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt13
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt24
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.kt124
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt20
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Context.kt267
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Delay.kt135
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt56
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt159
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Errors.kt228
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Limit.kt82
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Merge.kt178
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Transform.kt119
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Zip.kt317
-rw-r--r--kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt134
-rw-r--r--kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt30
-rw-r--r--kotlinx-coroutines-core/common/src/flow/terminal/Count.kt39
-rw-r--r--kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt122
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt52
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Atomic.kt76
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt28
-rw-r--r--kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt73
-rw-r--r--kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt316
-rw-r--r--kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt24
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt9
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Scopes.kt44
-rw-r--r--kotlinx-coroutines-core/common/src/internal/SegmentQueue.kt179
-rw-r--r--kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt44
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Symbol.kt17
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt19
-rw-r--r--kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt65
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt9
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt14
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt159
-rw-r--r--kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt41
-rw-r--r--kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt146
-rw-r--r--kotlinx-coroutines-core/common/src/selects/Select.kt440
-rw-r--r--kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt69
-rw-r--r--kotlinx-coroutines-core/common/src/selects/WhileSelect.kt32
-rw-r--r--kotlinx-coroutines-core/common/src/sync/Mutex.kt401
-rw-r--r--kotlinx-coroutines-core/common/src/sync/Semaphore.kt211
-rw-r--r--kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt94
-rw-r--r--kotlinx-coroutines-core/common/test/AsyncLazyTest.kt187
-rw-r--r--kotlinx-coroutines-core/common/test/AsyncTest.kt275
-rw-r--r--kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt126
-rw-r--r--kotlinx-coroutines-core/common/test/AwaitTest.kt354
-rw-r--r--kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt96
-rw-r--r--kotlinx-coroutines-core/common/test/CancellableContinuationTest.kt119
-rw-r--r--kotlinx-coroutines-core/common/test/CancellableResumeTest.kt122
-rw-r--r--kotlinx-coroutines-core/common/test/CompletableDeferredTest.kt198
-rw-r--r--kotlinx-coroutines-core/common/test/CompletableJobTest.kt49
-rw-r--r--kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt78
-rw-r--r--kotlinx-coroutines-core/common/test/CoroutineExceptionHandlerTest.kt47
-rw-r--r--kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt280
-rw-r--r--kotlinx-coroutines-core/common/test/CoroutinesTest.kt335
-rw-r--r--kotlinx-coroutines-core/common/test/DelayTest.kt58
-rw-r--r--kotlinx-coroutines-core/common/test/EnsureActiveTest.kt77
-rw-r--r--kotlinx-coroutines-core/common/test/ExperimentalDispatchModeTest.kt102
-rw-r--r--kotlinx-coroutines-core/common/test/FailedJobTest.kt62
-rw-r--r--kotlinx-coroutines-core/common/test/JobStatesTest.kt162
-rw-r--r--kotlinx-coroutines-core/common/test/JobTest.kt236
-rw-r--r--kotlinx-coroutines-core/common/test/LaunchLazyTest.kt71
-rw-r--r--kotlinx-coroutines-core/common/test/NonCancellableTest.kt137
-rw-r--r--kotlinx-coroutines-core/common/test/ParentCancellationTest.kt171
-rw-r--r--kotlinx-coroutines-core/common/test/SupervisorTest.kt240
-rw-r--r--kotlinx-coroutines-core/common/test/TestBase.common.kt81
-rw-r--r--kotlinx-coroutines-core/common/test/Try.kt29
-rw-r--r--kotlinx-coroutines-core/common/test/UnconfinedTest.kt113
-rw-r--r--kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt67
-rw-r--r--kotlinx-coroutines-core/common/test/WithContextTest.kt381
-rw-r--r--kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt241
-rw-r--r--kotlinx-coroutines-core/common/test/WithTimeoutTest.kt213
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt213
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt194
-rw-r--r--kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt216
-rw-r--r--kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt38
-rw-r--r--kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt37
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt39
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ChannelReceiveOrClosedTest.kt135
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt562
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt128
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt92
-rw-r--r--kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt43
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ProduceConsumeTest.kt59
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ProduceTest.kt168
-rw-r--r--kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt285
-rw-r--r--kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt50
-rw-r--r--kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt25
-rw-r--r--kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt77
-rw-r--r--kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt282
-rw-r--r--kotlinx-coroutines-core/common/test/flow/IdFlowTest.kt64
-rw-r--r--kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt68
-rw-r--r--kotlinx-coroutines-core/common/test/flow/VirtualTime.kt85
-rw-r--r--kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt220
-rw-r--r--kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt163
-rw-r--r--kotlinx-coroutines-core/common/test/flow/channels/FlowCallbackTest.kt47
-rw-r--r--kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt77
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/BufferTest.kt201
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt148
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/CombineParametersTestBase.kt164
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/CombineTest.kt277
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/ConflateTest.kt27
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt201
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/DistinctUntilChangedTest.kt97
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt61
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/DropWhileTest.kt51
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt83
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FilterTrivialTest.kt43
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlatMapBaseTest.kt91
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlatMapConcatTest.kt39
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlatMapLatestTest.kt137
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt94
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt92
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt91
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt39
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlattenMergeTest.kt54
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlowContextOptimizationsTest.kt116
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt317
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/IndexedTest.kt45
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt51
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/MapTest.kt49
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt184
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt51
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/OnStartTest.kt17
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt107
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt276
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt68
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/TakeTest.kt113
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/TakeWhileTest.kt67
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/TransformLatestTest.kt172
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/TransformTest.kt20
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/ZipTest.kt240
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/CollectLatestTest.kt56
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/CountTest.kt47
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt86
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt55
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/LaunchFlow.kt98
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/LaunchInTest.kt56
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt75
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/SingleTest.kt66
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/ToCollectionTest.kt31
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt391
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectBiasTest.kt44
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectBuilderImplTest.kt117
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectDeferredTest.kt173
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectJobTest.kt67
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectLinkedListChannelTest.kt29
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt42
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectMutexTest.kt56
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt435
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectTimeoutTest.kt89
-rw-r--r--kotlinx-coroutines-core/common/test/sync/MutexTest.kt109
-rw-r--r--kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt171
-rw-r--r--kotlinx-coroutines-core/js/npm/README.md19
-rw-r--r--kotlinx-coroutines-core/js/npm/package.json26
-rw-r--r--kotlinx-coroutines-core/js/src/CompletionHandler.kt30
-rw-r--r--kotlinx-coroutines-core/js/src/CoroutineContext.kt53
-rw-r--r--kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt12
-rw-r--r--kotlinx-coroutines-core/js/src/Debug.kt24
-rw-r--r--kotlinx-coroutines-core/js/src/Dispatchers.kt30
-rw-r--r--kotlinx-coroutines-core/js/src/EventLoop.kt27
-rw-r--r--kotlinx-coroutines-core/js/src/Exceptions.kt66
-rw-r--r--kotlinx-coroutines-core/js/src/JSDispatcher.kt163
-rw-r--r--kotlinx-coroutines-core/js/src/Promise.kt70
-rw-r--r--kotlinx-coroutines-core/js/src/Runnable.kt26
-rw-r--r--kotlinx-coroutines-core/js/src/SchedulerTask.kt16
-rw-r--r--kotlinx-coroutines-core/js/src/Window.kt63
-rw-r--r--kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt10
-rw-r--r--kotlinx-coroutines-core/js/src/internal/Concurrent.kt18
-rw-r--r--kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt98
-rw-r--r--kotlinx-coroutines-core/js/src/internal/LinkedList.kt160
-rw-r--r--kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt10
-rw-r--r--kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt22
-rw-r--r--kotlinx-coroutines-core/js/src/internal/Synchronized.kt20
-rw-r--r--kotlinx-coroutines-core/js/src/internal/SystemProps.kt7
-rw-r--r--kotlinx-coroutines-core/js/src/internal/ThreadContext.kt9
-rw-r--r--kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt12
-rw-r--r--kotlinx-coroutines-core/js/test/MessageQueueTest.kt87
-rw-r--r--kotlinx-coroutines-core/js/test/PromiseTest.kt77
-rw-r--r--kotlinx-coroutines-core/js/test/SetTimeoutDispatcherTest.kt53
-rw-r--r--kotlinx-coroutines-core/js/test/TestBase.kt109
-rw-r--r--kotlinx-coroutines-core/js/test/internal/LinkedListTest.kt47
-rw-r--r--kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro10
-rw-r--r--kotlinx-coroutines-core/jvm/src/Builders.kt95
-rw-r--r--kotlinx-coroutines-core/jvm/src/CommonPool.kt141
-rw-r--r--kotlinx-coroutines-core/jvm/src/CompletionHandler.kt22
-rw-r--r--kotlinx-coroutines-core/jvm/src/CoroutineContext.kt85
-rw-r--r--kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt41
-rw-r--r--kotlinx-coroutines-core/jvm/src/Debug.kt122
-rw-r--r--kotlinx-coroutines-core/jvm/src/DebugStrings.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt145
-rw-r--r--kotlinx-coroutines-core/jvm/src/Dispatchers.kt110
-rw-r--r--kotlinx-coroutines-core/jvm/src/EventLoop.kt49
-rw-r--r--kotlinx-coroutines-core/jvm/src/Exceptions.kt85
-rw-r--r--kotlinx-coroutines-core/jvm/src/Executors.kt148
-rw-r--r--kotlinx-coroutines-core/jvm/src/Future.kt54
-rw-r--r--kotlinx-coroutines-core/jvm/src/Runnable.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/src/SchedulerTask.kt18
-rw-r--r--kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt174
-rw-r--r--kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt90
-rw-r--r--kotlinx-coroutines-core/jvm/src/TimeSource.kt69
-rw-r--r--kotlinx-coroutines-core/jvm/src/channels/Actor.kt176
-rw-r--r--kotlinx-coroutines-core/jvm/src/channels/Channels.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt111
-rw-r--r--kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt21
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt36
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt81
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt98
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt678
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt114
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt11
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt206
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt131
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt10
-rw-r--r--kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt1019
-rw-r--r--kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt228
-rw-r--r--kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt123
-rw-r--r--kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt202
-rw-r--r--kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt296
-rw-r--r--kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt45
-rw-r--r--kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt148
-rw-r--r--kotlinx-coroutines-core/jvm/test/AwaitJvmTest.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/AwaitStressTest.kt129
-rw-r--r--kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt23
-rw-r--r--kotlinx-coroutines-core/jvm/test/CancellableContinuationResumeCloseStressTest.kt68
-rw-r--r--kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt55
-rw-r--r--kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt57
-rw-r--r--kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt94
-rw-r--r--kotlinx-coroutines-core/jvm/test/CoroutinesJvmTest.kt39
-rw-r--r--kotlinx-coroutines-core/jvm/test/DebugThreadNameTest.kt73
-rw-r--r--kotlinx-coroutines-core/jvm/test/DefaultExecutorStressTest.kt38
-rw-r--r--kotlinx-coroutines-core/jvm/test/DelayJvmTest.kt72
-rw-r--r--kotlinx-coroutines-core/jvm/test/EventLoopsTest.kt150
-rw-r--r--kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt105
-rw-r--r--kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt77
-rw-r--r--kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt135
-rw-r--r--kotlinx-coroutines-core/jvm/test/IODispatcherTest.kt25
-rw-r--r--kotlinx-coroutines-core/jvm/test/JobActivationStressTest.kt71
-rw-r--r--kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt60
-rw-r--r--kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt80
-rw-r--r--kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt97
-rw-r--r--kotlinx-coroutines-core/jvm/test/JobStressTest.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/JobStructuredJoinStressTest.kt33
-rw-r--r--kotlinx-coroutines-core/jvm/test/JoinStressTest.kt101
-rw-r--r--kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt165
-rw-r--r--kotlinx-coroutines-core/jvm/test/TestBase.kt219
-rw-r--r--kotlinx-coroutines-core/jvm/test/TestBaseTest.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/TestSecurityManager.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt116
-rw-r--r--kotlinx-coroutines-core/jvm/test/ThreadLocalTest.kt233
-rw-r--r--kotlinx-coroutines-core/jvm/test/Threads.kt60
-rw-r--r--kotlinx-coroutines-core/jvm/test/UnconfinedConcurrentStressTest.kt50
-rw-r--r--kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt158
-rw-r--r--kotlinx-coroutines-core/jvm/test/WithDefaultContextTest.kt33
-rw-r--r--kotlinx-coroutines-core/jvm/test/WithTimeoutChildDipspatchStressTest.kt32
-rw-r--r--kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt67
-rw-r--r--kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullThreadDispatchTest.kt82
-rw-r--r--kotlinx-coroutines-core/jvm/test/WithTimeoutThreadDispatchTest.kt85
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt82
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt184
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt64
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt159
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt70
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelAtomicCancelStressTest.kt161
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt110
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt173
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelsConsumeTest.kt908
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt25
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt90
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt113
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/DoubleChannelCloseStressTest.kt30
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt65
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ProduceConsumeJvmTest.kt62
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/SendReceiveJvmStressTest.kt48
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/SimpleSendReceiveJvmTest.kt55
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt167
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/TickerChannelTest.kt65
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt42
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt73
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/JobBasicCancellationTest.kt159
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionHandlingTest.kt380
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionsStressTest.kt66
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/JobNestedExceptionsTest.kt120
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/ProduceExceptionsTest.kt169
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt74
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt165
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt99
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt87
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt316
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt50
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt85
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt108
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/WithContextExceptionHandlingTest.kt281
-rw-r--r--kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt127
-rw-r--r--kotlinx-coroutines-core/jvm/test/flow/CombineStressTest.kt53
-rw-r--r--kotlinx-coroutines-core/jvm/test/flow/FlatMapStressTest.kt102
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt16
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-05s.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt18
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt22
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt25
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt26
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt28
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt30
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt26
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt22
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt33
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt28
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt28
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt31
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt42
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt32
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt32
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt23
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt21
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt22
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt24
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt12
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt31
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt23
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt26
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt14
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt38
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt22
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt21
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt33
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt32
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt31
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt12
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-02.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-03.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-04.kt28
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt26
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt24
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt14
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-08.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-09.kt23
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt26
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-11.kt16
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-12.kt23
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt22
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt23
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-15.kt25
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-19.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-20.kt16
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-21.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-22.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-23.kt24
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-24.kt24
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt24
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-27.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-28.kt27
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt25
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt26
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-32.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-33.kt21
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-35.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-flow-36.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt44
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt39
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt33
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt34
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt54
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt36
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt30
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt23
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt35
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt36
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt36
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt39
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt37
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt40
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt55
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt81
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/CancellationTimeOutsGuideTest.kt87
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/ChannelsGuideTest.kt123
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/ComposingGuideTest.kt56
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt110
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/ExceptionsGuideTest.kt89
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/FlowGuideTest.kt381
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt0
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt69
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt63
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt232
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/FastServiceLoaderTest.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt161
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt74
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt89
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt82
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueStressTest.kt119
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueTest.kt76
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/SegmentBasedQueue.kt72
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/SegmentQueueLCStressTest.kt30
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt99
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt96
-rw-r--r--kotlinx-coroutines-core/jvm/test/linearizability/ChannelIsClosedLCStressTest.kt54
-rw-r--r--kotlinx-coroutines-core/jvm/test/linearizability/ChannelLCStressTest.kt73
-rw-r--r--kotlinx-coroutines-core/jvm/test/linearizability/LinTesting.kt138
-rw-r--r--kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLCStressTest.kt72
-rw-r--r--kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLCStressTest.kt93
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherRaceStressTest.kt62
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherStressTest.kt126
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt224
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/BlockingIOTerminationStressTest.kt38
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt188
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt90
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerShrinkTest.kt124
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt120
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt150
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/LimitingCoroutineDispatcherStressTest.kt55
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt49
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt108
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt46
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt15
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt123
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt144
-rw-r--r--kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt76
-rw-r--r--kotlinx-coroutines-core/jvm/test/selects/SelectMemoryLeakStressTest.kt59
-rw-r--r--kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt34
-rw-r--r--kotlinx-coroutines-core/jvm/test/selects/SelectPhilosophersStressTest.kt65
-rw-r--r--kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt29
-rw-r--r--kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt96
-rw-r--r--kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt403
-rw-r--r--kotlinx-coroutines-core/native/src/Builders.kt80
-rw-r--r--kotlinx-coroutines-core/native/src/CompletionHandler.kt22
-rw-r--r--kotlinx-coroutines-core/native/src/CoroutineContext.kt40
-rw-r--r--kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt12
-rw-r--r--kotlinx-coroutines-core/native/src/Debug.kt18
-rw-r--r--kotlinx-coroutines-core/native/src/Dispatchers.kt30
-rw-r--r--kotlinx-coroutines-core/native/src/EventLoop.kt22
-rw-r--r--kotlinx-coroutines-core/native/src/Exceptions.kt66
-rw-r--r--kotlinx-coroutines-core/native/src/Runnable.kt26
-rw-r--r--kotlinx-coroutines-core/native/src/SchedulerTask.kt15
-rw-r--r--kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt11
-rw-r--r--kotlinx-coroutines-core/native/src/internal/Concurrent.kt21
-rw-r--r--kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt104
-rw-r--r--kotlinx-coroutines-core/native/src/internal/LinkedList.kt158
-rw-r--r--kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt10
-rw-r--r--kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt21
-rw-r--r--kotlinx-coroutines-core/native/src/internal/Synchronized.kt20
-rw-r--r--kotlinx-coroutines-core/native/src/internal/SystemProps.kt7
-rw-r--r--kotlinx-coroutines-core/native/src/internal/ThreadContext.kt9
-rw-r--r--kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt15
-rw-r--r--kotlinx-coroutines-core/native/test/DelayExceptionTest.kt39
-rw-r--r--kotlinx-coroutines-core/native/test/TestBase.kt100
-rw-r--r--kotlinx-coroutines-core/native/test/WorkerTest.kt35
-rw-r--r--kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt47
-rw-r--r--kotlinx-coroutines-core/npm/README.md19
-rw-r--r--kotlinx-coroutines-core/npm/package.json26
-rw-r--r--kotlinx-coroutines-debug/README.md167
-rw-r--r--kotlinx-coroutines-debug/build.gradle39
-rw-r--r--kotlinx-coroutines-debug/src/AgentPremain.kt30
-rw-r--r--kotlinx-coroutines-debug/src/CoroutineInfo.kt113
-rw-r--r--kotlinx-coroutines-debug/src/DebugProbes.kt130
-rw-r--r--kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt417
-rw-r--r--kotlinx-coroutines-debug/src/internal/NoOpProbes.kt19
-rw-r--r--kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt66
-rw-r--r--kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt88
-rw-r--r--kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt159
-rw-r--r--kotlinx-coroutines-debug/test/DebugProbesTest.kt103
-rw-r--r--kotlinx-coroutines-debug/test/DebugTestBase.kt32
-rw-r--r--kotlinx-coroutines-debug/test/Example.kt32
-rw-r--r--kotlinx-coroutines-debug/test/RecoveryExample.kt31
-rw-r--r--kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt181
-rw-r--r--kotlinx-coroutines-debug/test/SanitizedProbesTest.kt147
-rw-r--r--kotlinx-coroutines-debug/test/ScopedBuildersTest.kt48
-rw-r--r--kotlinx-coroutines-debug/test/StartModeProbesTest.kt141
-rw-r--r--kotlinx-coroutines-debug/test/StracktraceUtils.kt109
-rw-r--r--kotlinx-coroutines-debug/test/TestRuleExample.kt42
-rw-r--r--kotlinx-coroutines-debug/test/ToStringTest.kt148
-rw-r--r--kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt55
-rw-r--r--kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt103
-rw-r--r--kotlinx-coroutines-test/README.md457
-rw-r--r--kotlinx-coroutines-test/build.gradle3
-rw-r--r--kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro9
-rw-r--r--kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory1
-rw-r--r--kotlinx-coroutines-test/src/DelayController.kt125
-rw-r--r--kotlinx-coroutines-test/src/TestBuilders.kt96
-rw-r--r--kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt215
-rw-r--r--kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt63
-rw-r--r--kotlinx-coroutines-test/src/TestCoroutineScope.kt72
-rw-r--r--kotlinx-coroutines-test/src/TestDispatchers.kt38
-rw-r--r--kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt77
-rw-r--r--kotlinx-coroutines-test/test/TestBuildersTest.kt132
-rw-r--r--kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt39
-rw-r--r--kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt142
-rw-r--r--kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt18
-rw-r--r--kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt26
-rw-r--r--kotlinx-coroutines-test/test/TestDispatchersTest.kt89
-rw-r--r--kotlinx-coroutines-test/test/TestModuleHelpers.kt22
-rw-r--r--kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt79
-rw-r--r--kotlinx-coroutines-test/test/TestRunBlockingTest.kt397
-rw-r--r--license/NOTICE.txt8
-rw-r--r--license/third_party/minima_LICENSE.txt21
-rw-r--r--publication-validator/README.md9
-rw-r--r--publication-validator/build.gradle35
-rw-r--r--publication-validator/gradle.properties1
-rw-r--r--publication-validator/gradle/wrapper/gradle-wrapper.jarbin0 -> 56177 bytes
-rw-r--r--publication-validator/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xpublication-validator/gradlew172
-rw-r--r--publication-validator/gradlew.bat84
-rw-r--r--publication-validator/settings.gradle8
-rw-r--r--publication-validator/src/test/kotlin/kotlinx/coroutines/tools/MavenPublicationValidator.kt53
-rw-r--r--publication-validator/src/test/kotlin/kotlinx/coroutines/tools/NpmPublicationValidator.kt71
-rw-r--r--reactive/README.md10
-rw-r--r--reactive/coroutines-guide-reactive.md1078
-rw-r--r--reactive/kotlinx-coroutines-reactive/README.md48
-rw-r--r--reactive/kotlinx-coroutines-reactive/build.gradle34
-rw-r--r--reactive/kotlinx-coroutines-reactive/package.list1
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Await.kt161
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Channel.kt116
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt15
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Convert.kt27
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Migration.kt36
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Publish.kt292
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt227
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt79
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt150
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt132
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublishTest.kt186
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt148
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherBackpressureTest.kt61
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherCompletionStressTest.kt36
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt32
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt61
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/RangePublisherBufferedTest.kt31
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/RangePublisherTest.kt48
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/ReactiveStreamTckTest.kt51
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/UnboundedIntegerIncrementPublisherTest.kt57
-rw-r--r--reactive/kotlinx-coroutines-reactor/README.md42
-rw-r--r--reactive/kotlinx-coroutines-reactor/build.gradle23
-rw-r--r--reactive/kotlinx-coroutines-reactor/package.list9
-rw-r--r--reactive/kotlinx-coroutines-reactor/resources/META-INF/services/kotlinx.coroutines.reactive.ContextInjector1
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Convert.kt56
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Flux.kt77
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Migration.kt13
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Mono.kt89
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt56
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt22
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt28
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Scheduler.kt56
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/BackpressureTest.kt39
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/Check.kt44
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt127
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/FlowAsFluxTest.kt27
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/FluxCompletionStressTest.kt37
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/FluxMultiTest.kt86
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt207
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/FluxTest.kt133
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/MonoTest.kt251
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt111
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/SchedulerTest.kt36
-rw-r--r--reactive/kotlinx-coroutines-rx-example/build.gradle15
-rw-r--r--reactive/kotlinx-coroutines-rx-example/src/main.kt52
-rw-r--r--reactive/kotlinx-coroutines-rx2/README.md78
-rw-r--r--reactive/kotlinx-coroutines-rx2/build.gradle36
-rw-r--r--reactive/kotlinx-coroutines-rx2/package.list14
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxAwait.kt221
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt14
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxChannel.kt101
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt82
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxConvert.kt113
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt55
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt85
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxObservable.kt211
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt53
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxSingle.kt82
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/BackpressureTest.kt39
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/Check.kt66
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt167
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ConvertTest.kt157
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/FlowAsObservableTest.kt143
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt133
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/FlowableTest.kt128
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt153
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/IterableFlowAsFlowableTckTest.kt37
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt297
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableCompletionStressTest.kt36
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt133
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt92
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt206
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt51
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableTest.kt132
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/SchedulerTest.kt36
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/SingleTest.kt251
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt31
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt32
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt26
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt20
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt30
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt18
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt22
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt25
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt24
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt23
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt24
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt26
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt24
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt27
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt19
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt32
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt39
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt37
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/test/GuideReactiveTest.kt191
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/guide/test/ReactiveTestBase.kt47
-rw-r--r--settings.gradle47
-rw-r--r--site/README.md14
-rw-r--r--site/build.gradle32
-rwxr-xr-xsite/deploy.sh79
-rw-r--r--site/docs/Gemfile16
-rw-r--r--site/docs/Gemfile.lock64
-rw-r--r--site/docs/_config.yml17
-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.js31
-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.gradle9
-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/README.md11
-rw-r--r--ui/coroutines-guide-ui.md725
-rw-r--r--ui/kotlinx-coroutines-android/README.md10
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/build.gradle16
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/resources/META-INF/services/kotlinx.coroutines.CoroutineScope13
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt21
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/CustomizedRobolectricTest.kt55
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstMockedMainTest.kt44
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstRobolectricTest.kt55
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/MockedMainTest.kt7
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/RobolectricTest.kt6
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/TestComponent.kt28
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/build.gradle35
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/proguard-rules.pro8
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/AndroidManifest.xml23
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt155
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt26
-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.xml43
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml20
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 3056 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 5024 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2096 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 2858 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4569 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 7098 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 6464 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 10676 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 9250 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 15523 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.gradle26
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradle.properties23
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.jar0
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties5
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradlew172
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradlew.bat84
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/settings.gradle1
-rw-r--r--ui/kotlinx-coroutines-android/build.gradle107
-rw-r--r--ui/kotlinx-coroutines-android/example-app/.gitignore7
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/build.gradle34
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/proguard-rules.pro8
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/AndroidManifest.xml24
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt39
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml33
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml21
-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.pngbin0 -> 3418 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 4208 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2206 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 2555 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4842 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 6114 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 7718 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 10056 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 10486 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 14696 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.gradle26
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradle.properties23
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.jar0
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties5
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradlew160
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradlew.bat90
-rw-r--r--ui/kotlinx-coroutines-android/example-app/settings.gradle1
-rw-r--r--ui/kotlinx-coroutines-android/gradle/wrapper/gradle-wrapper.jar0
-rw-r--r--ui/kotlinx-coroutines-android/gradle/wrapper/gradle-wrapper.properties6
-rw-r--r--ui/kotlinx-coroutines-android/gradlew172
-rw-r--r--ui/kotlinx-coroutines-android/gradlew.bat84
-rw-r--r--ui/kotlinx-coroutines-android/package.list211
-rw-r--r--ui/kotlinx-coroutines-android/r8-test-common.pro12
-rw-r--r--ui/kotlinx-coroutines-android/r8-test-rules-no-optim.pro4
-rw-r--r--ui/kotlinx-coroutines-android/r8-test-rules.pro7
-rw-r--r--ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/proguard/coroutines.pro5
-rw-r--r--ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-from-1.6.0/coroutines.pro6
-rw-r--r--ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro5
-rw-r--r--ui/kotlinx-coroutines-android/resources/META-INF/proguard/coroutines.pro7
-rw-r--r--ui/kotlinx-coroutines-android/resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler1
-rw-r--r--ui/kotlinx-coroutines-android/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory1
-rw-r--r--ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt46
-rw-r--r--ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt195
-rw-r--r--ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt144
-rw-r--r--ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt61
-rw-r--r--ui/kotlinx-coroutines-javafx/README.md10
-rw-r--r--ui/kotlinx-coroutines-javafx/build.gradle4
-rw-r--r--ui/kotlinx-coroutines-javafx/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory1
-rw-r--r--ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt150
-rw-r--r--ui/kotlinx-coroutines-javafx/test/JavaFxTest.kt37
-rw-r--r--ui/kotlinx-coroutines-javafx/test/examples/FxExampleApp.kt133
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt69
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt72
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt72
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt63
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt63
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt55
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt61
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt62
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt81
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt82
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt85
-rw-r--r--ui/kotlinx-coroutines-swing/README.md10
-rw-r--r--ui/kotlinx-coroutines-swing/build.gradle7
-rw-r--r--ui/kotlinx-coroutines-swing/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory1
-rw-r--r--ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt81
-rw-r--r--ui/kotlinx-coroutines-swing/test/SwingTest.kt86
-rw-r--r--ui/kotlinx-coroutines-swing/test/examples/SwingExampleApp.kt56
-rw-r--r--ui/kotlinx-coroutines-swing/test/examples/swing-example.kt39
-rw-r--r--ui/ui-example-android.pngbin0 -> 4619 bytes
-rw-r--r--ui/ui-example-javafx.pngbin0 -> 1853 bytes
1040 files changed, 95642 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..c447f28b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.idea
+*.iml
+.gradle
+.gradletasknamecache
+build
+out
+target
+local.properties \ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 00000000..4db08d9d
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,951 @@
+# Change log for kotlinx.coroutines
+
+## Version 1.3.1
+
+This is a minor update with various fixes:
+* Flow: Fix recursion in combineTransform<T1, T2, R> (#1466).
+* Fixed race in the Semaphore (#1477).
+* Repaired some of ListenableFuture.kt's cancellation corner cases (#1441).
+* Consistently unwrap exception in slow path of CompletionStage.asDeferred (#1479).
+* Various fixes in documentation (#1496, #1476, #1470, #1468).
+* Various cleanups and additions in tests.
+
+Note: Kotlin/Native artifacts are now published with Gradle metadata format version 1.0, so you will need
+Gradle version 5.3 or later to use this version of kotlinx.coroutines in your Kotlin/Native project.
+
+## Version 1.3.0
+
+### Flow
+
+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.
+
+### Changelog
+
+* A new [guide section](/docs/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).
+* Reactive Flow integrations enclosing files are renamed for better interoperability with Java.
+* Default buffer size in all Flow operators is increased to 64.
+* Kotlin updated to 1.3.50.
+
+## Version 1.3.0-RC2
+
+### Flow improvements
+* Operators for UI programming are reworked for the sake of consistency, naming scheme for operator overloads is introduced:
+ * `combineLatest` is deprecated in the favor of `combine`.
+ * `combineTransform` operator for non-trivial transformations (#1224).
+ * Top-level `combine` and `combineTransform` overloads for multiple flows (#1262).
+ * `switchMap` is deprecated. `flatMapLatest`, `mapLatest` and `transformLatest` are introduced instead (#1335).
+ * `collectLatest` terminal operator (#1269).
+
+* Improved cancellation support in `flattenMerge` (#1392).
+* `channelFlow` cancellation does not leak to the parent (#1334).
+* Fixed flow invariant enforcement for `suspend fun main` (#1421).
+* `delayEach` and `delayFlow` are deprecated (#1429).
+
+### General changes
+* Integration with Reactor context
+ * Propagation of the coroutine context of `await` calls into Mono/Flux builder.
+ * Publisher.asFlow propagates coroutine context from `collect` call to the Publisher.
+ * New `Flow.asFlux ` builder.
+
+* ServiceLoader-code is adjusted to avoid I/O on the Main thread on newer (3.6.0+) Android toolchain.
+* Stacktrace recovery support for minified builds on Android (#1416).
+* Guava version in `kotlinx-coroutines-guava` updated to `28.0`.
+* `setTimeout`-based JS dispatcher for platforms where `process` is unavailable (#1404).
+* Native, JS and common modules are added to `kotlinx-coroutines-bom`.
+* Fixed bug with ignored `acquiredPermits` in `Semaphore` (#1423).
+
+## Version 1.3.0-RC
+
+### Flow
+
+* Core `Flow` API is promoted to stable
+* New basic `Flow` operators: `withIndex`, `collectIndexed`, `distinctUntilChanged` overload
+* New core `Flow` operators: `onStart` and `onCompletion`
+* `ReceiveChannel.consumeAsFlow` and `emitAll` (#1340)
+
+### General changes
+
+* Kotlin updated to 1.3.41
+* Added `kotlinx-coroutines-bom` with Maven Bill of Materials (#1110)
+* Reactive integrations are seriously improved
+ * All builders now are top-level functions instead of extensions on `CoroutineScope` and prohibit `Job` instance in their context to simplify lifecycle management
+ * Fatal exceptions are handled consistently (#1297)
+ * Integration with Reactor Context added (#284)
+* Stacktrace recovery for `suspend fun main` (#1328)
+* `CoroutineScope.cancel` extension with message (#1338)
+* Protection against non-monotonic clocks in `delay` (#1312)
+* `Duration.ZERO` is handled properly in JDK 8 extensions (#1349)
+* Library code is adjusted to be more minification-friendly
+
+## Version 1.3.0-M2
+
+ * Kotlin updated to 1.3.40.
+ * `Flow` exception transparency concept.
+ * New declarative `Flow` operators: `onCompletion`, `catch`, `retryWhen`, `launchIn`. `onError*` operators are deprecated in favour of `catch`. (#1263)
+ * `Publisher.asFlow` is integrated with `buffer` operator.
+ * `Publisher.openSubscription` default request size is `1` instead of `0` (#1267).
+
+## Version 1.3.0-M1
+
+Flow:
+ * Core `Flow` interfaces and operators are graduated from preview status to experimental.
+ * Context preservation invariant rework (#1210).
+ * `channelFlow` and `callbackFlow` replacements for `flowViaChannel` for concurrent flows or callback-based APIs.
+ * `flow` prohibits emissions from non-scoped coroutines by default and recommends to use `channelFlow` instead to avoid most of the concurrency-related bugs.
+ * Flow cannot be implemented directly
+ * `AbstractFlow` is introduced for extension (e.g. for managing state) and ensures all context preservation invariants.
+ * Buffer size is decoupled from all operators that imply channel usage (#1233)
+ * `buffer` operator can be used to adjust buffer size of any buffer-dependent operator (e.g. `channelFlow`, `flowOn` and `flatMapMerge`).
+ * `conflate` operator is introduced.
+ * Flow performance is significantly improved.
+ * New operators: `scan`, `scanReduce`, `first`, `emitAll`.
+ * `flowWith` and `flowViaChannel` are deprecated.
+ * `retry` ignores cancellation exceptions from upstream when the flow was externally cancelled (#1122).
+ * `combineLatest` overloads for multiple flows (#1193).
+ * Fixed numerical overflow in `drop` operator.
+
+Channels:
+ * `consumeEach` is promoted to experimental API (#1080).
+ * Conflated channels always deliver the latest value after closing (#332, #1235).
+ * Non-suspending `ChannelIterator.next` to improve iteration performance (#1162).
+ * Channel exception types are consistent with `produce` and are no longer swallowed as cancellation exceptions in case of programmatic errors (#957, #1128).
+ * All operators on channels (that were prone to coroutine leaks) are deprecated in the favor of `Flow`.
+
+General changes:
+ * Kotlin updated to 1.3.31
+ * `Semaphore` implementation (#1088)
+ * Loading of `Dispatchers.Main` is tweaked so the latest version of R8 can completely remove I/O when loading it (#1231).
+ * Performace of all JS dispatchers is significantly improved (#820).
+ * `withContext` checks cancellation status on exit to make reasoning about sequential concurrent code easier (#1177).
+ * Consistent exception handling mechanism for complex hierarchies (#689).
+ * Convenient overload for `CoroutinesTimeout.seconds` (#1184).
+ * Fix cancellation bug in onJoin (#1130).
+ * Prevent internal names clash that caused errors for ProGuard (#1159).
+ * POSIX's `nanosleep` as `delay` in `runBlocking ` in K/N (#1225).
+
+## Version 1.2.2
+
+* Kotlin updated to 1.3.40.
+
+## Version 1.2.1
+
+Major:
+ * Infrastructure for testing coroutine-specific code in `kotlinx-coroutines-test`: `runBlockingTest`, `TestCoroutineScope` and `TestCoroutineDispatcher`, contributed by Sean McQuillan (@objcode). Obsolete `TestCoroutineContext` from `kotlinx-coroutines-core` is deprecated.
+ * `Job.asCompletableFuture` extension in jdk8 module (#1113).
+
+Flow improvements:
+ * `flowViaChannel` rework: block parameter is no longer suspending, but provides `CoroutineScope` receiver and allows conflated channel (#1081, #1112).
+ * New operators: `switchMap`, `sample`, `debounce` (#1107).
+ * `consumerEach` is deprecated on `Publisher`, `ObservableSource` and `MaybeSource`, `collect` extension is introduced instead (#1080).
+
+Other:
+ * Race in Job.join and concurrent cancellation is fixed (#1123).
+ * Stacktrace recovery machinery improved: cycle detection works through recovered exceptions, cancellation exceptions are recovered on cancellation fast-path.
+ * Atomicfu-related bug fixes: publish transformed artifacts, do not propagate transitive atomicfu dependency (#1064, #1116).
+ * Publication to NPM fixed (#1118).
+ * Misplaced resources are removed from the final jar (#1131).
+
+## Version 1.2.0
+
+ * Kotlin updated to 1.3.30.
+ * New API: `CancellableContinuation.resume` with `onCancelling` lambda (#1044) to consistently handle closeable resources.
+ * Play services task version updated to 16.0.1.
+ * `ReceiveChannel.isEmpty` is no longer deprecated
+
+A lot of `Flow` improvements:
+ * Purity property is renamed to context preservation and became more restrictive.
+ * `zip` and `combineLatest` operators.
+ * Integration with RxJava2
+ * `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).
+
+## Version 1.2.0-alpha-2
+
+This release contains major [feature preview](/docs/compatibility.md#flow-preview-api): cold streams aka `Flow` (#254).
+
+Performance:
+* Performance of `Dispatcher.Main` initialization is significantly improved (#878).
+
+## Version 1.2.0-alpha
+
+* Major debug agent improvements. Real stacktraces are merged with coroutine stacktraces for running coroutines, merging heuristic is improved, API is cleaned up and is on its road to stabilization (#997).
+* `CoroutineTimeout` rule or JUnit4 is introduced to simplify coroutines debugging (#938).
+* Stacktrace recovery improvements. Exceptions with custom properties are no longer copied, `CopyableThrowable` interface is introduced, machinery is [documented](https://github.com/Kotlin/kotlinx.coroutines/blob/develop/docs/debugging.md) (#921, #950).
+* `Dispatchers.Unconfined`, `MainCoroutineDispatcher.immediate`, `MainScope` and `CoroutineScope.cancel` are promoted to stable API (#972).
+* `CompletableJob` is introduced (#971).
+* Structured concurrency is integrated into futures and listenable futures (#1008).
+* `ensurePresent` and `isPresent` extensions for `ThreadLocal` (#1028).
+* `ensureActive` extensions for `CoroutineContext`, `CoroutineScope` and `Job` (#963).
+* `SendChannel.isFull` and `ReceiveChannel.isEmpty` are deprecated (#1053).
+* `withContext` checks cancellation on entering (#962).
+* Operator `invoke` on `CoroutineDispatcher` (#428).
+* Java 8 extensions for `delay` and `withTimeout` now properly handle too large values (#428).
+* A global exception handler for fatal exceptions in coroutines is introduced (#808, #773).
+* Major improvements in cancellation machinery and exceptions delivery consistency. Cancel with custom exception is completely removed.
+* Kotlin version is updated to 1.3.21.
+* Do not use private API on newer Androids to handle exceptions (#822).
+
+Bug fixes:
+* Proper `select` support in debug agent (#931).
+* Proper `supervisorScope` support in debug agent (#915).
+* Throwing `initCause` does no longer trigger an internal error (#933).
+* Lazy actors are started when calling `close` in order to cleanup their resources (#939).
+* Minor bugs in reactive integrations are fixed (#1008).
+* Experimental scheduler shutdown sequence is fixed (#990).
+
+## Version 1.1.1
+
+* Maintenance release, no changes in the codebase
+* Kotlin is updated to 1.3.20
+* Gradle is updated to 4.10
+* Native module is published with Gradle metadata v0.4
+
+## Version 1.1.0
+
+* Kotlin version updated to 1.3.11.
+* Resumes to `CancellableContinuation` in the final state produce `IllegalStateException` (#901). This change does not affect #830, races between resume and cancellation do not lead to an exceptional situation.
+* `runBlocking` is integrated with `Dispatchers.Unconfined` by sharing an internal event loop. This change does not affect the semantics of the previously correct code but allows to mix multiple `runBlocking` and unconfined tasks (#860).
+
+## Version 1.1.0-alpha
+
+### Major improvements in coroutines testing and debugging
+* New module: [`kotlinx-coroutines-debug`](https://github.com/Kotlin/kotlinx.coroutines/blob/master/core/kotlinx-coroutines-debug/README.md). Debug agent that improves coroutines stacktraces, allows to print all active coroutines and its hierarchies and can be installed as Java agent.
+* New module: [`kotlinx-coroutines-test`](https://github.com/Kotlin/kotlinx.coroutines/blob/master/core/kotlinx-coroutines-test/README.md). Allows setting arbitrary `Dispatchers.Main` implementation for tests (#810).
+* Stacktrace recovery mechanism. Exceptions from coroutines are recovered from current coroutine stacktraces to simplify exception diagnostic. Enabled in debug mode, controlled by `kotlinx.coroutines.debug` system property (#493).
+
+### Other improvements
+* `MainScope` factory and `CoroutineScope.cancel` extension (#829). One line `CoroutineScope` integration!
+* `CancellableContinuation` race between `resumeWithException` and `cancel` is addressed, exceptions during cancellation are no longer reported to exception handler (#830, #892).
+* `Dispatchers.Default` now consumes much less CPU on JVM (#840).
+* Better diagnostic and fast failure if an uninitialized dispatcher is used (#880).
+* Conflated channel becomes linearizable.
+* Fixed inconsistent coroutines state when the result of the coroutine had type `DisposableHandle` (#835).
+* Fixed `JavaFx` initialization bug (#816).
+* `TimeoutCancellationException` is thrown by `withTimeout` instead of `CancellationException` if negative timeout is supplied (#870).
+* Kotlin/Native single-threaded workers support: coroutines can be safely used in multiple independent K/N workers.
+* jsdom support in `Dispatchers.Default` on JS.
+* rxFlowable generic parameter is now restricted with Any.
+* Guava 27 support in `kotlinx-coroutines-guava`.
+* Coroutines are now built with progressive mode.
+* Various fixes in the documentation.
+
+## Version 1.0.1
+
+* Align `publisher` implementation with Reactive TCK.
+* Reimplement `future` coroutine builders on top of `AbstractCoroutine` (#751).
+* Performance optimizations in `Dispatchers.Default` and `Dispatchers.IO`.
+* Use only public API during `JavaFx` instantiation, fixes warnings on Java 9 and build on Java 11 (#463).
+* Updated contract of `CancellableContinuation.resumeWithException` (documentation fix, see #712).
+* Check cancellation on fast-path of all in-place coroutine builders (`withContext`, `coroutineScope`, `supervisorScope`, `withTimeout` and `withTimeoutOrNull`).
+* Add optional prefix to thread names of `ExperimentalCoroutineDispatcher` (#661).
+* Fixed bug when `ExperimentalCoroutineDispatcher` could end up in inconsistent state if `Thread` constructor throws an exception (#748).
+
+## Version 1.0.0
+
+* All Kotlin dependencies updated to 1.3 release version.
+* Fixed potential memory leak in `HandlerDispatcher.scheduleResumeAfterDelay`, thanks @cbeyls.
+* `yield` support for `Unconfined` and immediate dispatchers (#737).
+* Various documentation improvements.
+
+## Version 1.0.0-RC1
+
+* Coroutines API is updated to Kotlin 1.3.
+* Deprecated API is removed or marked as `internal`.
+* Experimental and internal coroutine API is marked with corresponding `kotlin.experimental.Experimental` annotation. If you are using `@ExperimentalCoroutinesApi` or `@InternalCoroutinesApi` you should explicitly opt-in, otherwise compilation warning (or error) will be produced.
+* `Unconfined` dispatcher (and all dispatchers which support immediate invocation) forms event-loop on top of current thread, thus preventing all `StackOverflowError`s. `Unconfined` dispatcher is now much safer for the general use and may leave its experimental status soon (#704).
+* Significantly improved performance of suspending hot loops in `kotlinx.coroutines` (#537).
+* Proguard rules are embedded into coroutines JAR to assist jettifier (#657)
+* Fixed bug in shutdown sequence of `runBlocking` (#692).
+* `ReceiveChannel.receiveOrNull` is marked as obsolete and deprecated.
+* `Job.cancel(cause)` and `ReceiveChannel.cancel(cause)` are deprecated, `cancel()` returns `Unit` (#713).
+
+## Version 0.30.2
+
+* `Dispatchers.Main` is instantiated lazily (see #658 and #665).
+* Blocking coroutine dispatcher views are now shutdown properly (#678).
+* Prevent leaking Kotlin 1.3 from atomicfu dependency (#659).
+* Thread-pool based dispatcher factories are marked as obsolete (#261).
+* Fixed exception loss on `withContext` cancellation (#675).
+
+## Version 0.30.1
+
+Maintenance release:
+* Added `Dispatchers.Main` to common dispatchers, which can be used from Android, Swing and JavaFx projects if a corresponding integration library is added to dependencies.
+* With `Dispatchers.Main` improvement tooling bug in Android Studio #626 is mitigated, so Android users now can safely start the migration to the latest `kotlinx.coroutines` version.
+* Fixed bug with thread unsafety of shutdown sequence in `EventLoop`.
+* Experimental coroutine dispatcher now has `close` contract similar to Java `Executor`, so it can be safely instantiated and closed multiple times (affects only unit tests).
+* Atomicfu version is updated with fixes in JS transformer (see #609)
+
+## Version 0.30.0
+
+* **[Major]** Further improvements in exception handling &mdash; no failure exception is lost.
+ * `async` and async-like builders cancel parent on failure (it affects `CompletableDeferred`, and all reactive integration builders).
+ * This makes parallel decomposition exception-safe and reliable without having to rember about `awaitAll` (see #552).
+ * `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).
+ * 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).
+ * `-Dkotlinx.coroutines.scheduler=off` can be used to switch back to `CommonPool` for a time being (until deprecated CommonPool is removed).
+* Make `CoroutineStart.ATOMIC` experimental as it covers important use-case with resource cleanup in finally block (see #627).
+* Restored binary compatibility of `Executor.asCoroutineDispatcher` (see #629).
+* Fixed OOM in thread-pool dispatchers (see #571).
+* Check for cancellation when starting coroutine with `Dispatchers.Unconfined` (see #621).
+* A bunch of various performance optimizations and docs fixes, including contributions from @AlexanderPrendota, @PaulWoitaschek.
+
+## Version 0.27.0
+
+* **[Major]** Public API revision. All public API was reviewed and marked as preparation to `1.0` release:
+ 1. `@Deprecated` API. All API marked as deprecated will be removed in 1.0 release without replacement.
+ 2. `@ExperimentalCoroutinesApi` API. This API is experimental and may change in the future, but migration mechanisms will be provided. Signature, binary compatibility and semantics can be changed.
+ 3. `@InternalCoroutinesApi`. This API is intended to be used **only** from within `kotlinx.coroutines`. It can and will be changed, broken
+ and removed in the future releases without any warnings and migration aids. If you find yourself using this API, it is better to report
+ your use-case to Github issues, so decent, stable and well-tested alternative can be provided.
+ 4. `@ObsoleteCoroutinesApi`. This API has serious known flaws and will be replaced with a better alternative in the nearest releases.
+ 5. Regular public API. This API is proven to be stable and is not going to be changed. If at some point it will be discovered that such API
+ has unfixable design flaws, it will be gradually deprecated with proper replacement and migration aid, but won't be removed for at least a year.
+* **[Major]** Job state machine is reworked. It includes various performance improvements, fixes in
+data-races which could appear in a rare circumstances and consolidation of cancellation and exception handling.
+Visible consequences of include more robust exception handling for large coroutines hierarchies and for different kinds of `CancellationException`, transparent parallel decomposition and consistent view of coroutines hierarchy in terms of its state (see #220 and #585).
+* NIO, Quasar and Rx1 integration modules are removed with no replacement (see #595, #601, #603).
+* `withContext` is now aligned with structured concurrency and awaits for all launched tasks, its performance is significantly improved (see #553 and #617).
+* Added integration module with Play Services Task API. Thanks @SUPERCILEX and @lucasvalenteds for the contribution!
+* Integration with Rx2 now respects nullability in type constraints (see #347). Thanks @Dmitry-Borodin for the contribution!
+* `CompletableFuture.await` and `ListenableFuture.await` now propagate cancellation to the future (see #611).
+* Cancellation of `runBlocking` machinery is improved (see #589).
+* Coroutine guide is restructured and split to multiple files for the sake of simplicity.
+* `CoroutineScope` factory methods add `Job` if it is missing from the context to enforce structured concurrency (see #610).
+* `Handler.asCoroutineDispatcher` has a `name` parameter for better debugging (see #615).
+* Fixed bug when `CoroutineSchedule` was closed from one of its threads (see #612).
+* Exceptions from `CoroutineExceptionHandler` are reported by default exception handler (see #562).
+* `CoroutineName` is now available from common modules (see #570).
+* Update to Kotlin 1.2.70.
+
+## Version 0.26.1
+
+* Android `Main` dispatcher is `async` by default which may significantly improve UI performance. Contributed by @JakeWharton (see #427).
+* Fixed bug when lazily-started coroutine with registered cancellation handler was concurrently started and cancelled.
+* Improved termination sequence in IO dispatcher.
+* Fixed bug with `CoroutineScope.plus` operator (see #559).
+* Various fixes in the documentation. Thanks to @SUPERCILEX, @yorlov, @dualscyther and @soudmaijer!
+
+## Version 0.26.0
+
+* Major rework of `kotlinx.coroutines` concurrency model (see #410 for a full explanation of the rationale behind this change):
+ * All coroutine builders are now extensions on `CoroutineScope` and inherit its `coroutineContext`. Standalone builders are deprecated.
+ * As a consequence, all nested coroutines launched via builders now automatically establish parent-child relationship and inherit `CoroutineDispatcher`.
+ * All coroutine builders use `Dispatchers.Default` by default if `CoroutineInterceptor` is not present in their context.
+ * [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) became the first-class citizen in `kolinx.coroutines`.
+ * `withContext` `block` argument has `CoroutineScope` as a receiver.
+ * [GlobalScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/) is introduced to simplify migration to new API and to launch global-level coroutines.
+ * `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 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):
+ * Dispatcher names are consistent.
+ * Old dispatchers including `CommonPool` are deprecated.
+* Fixed bug with JS error in rare cases in `invokeOnCompletion(onCancelling = true)`.
+* Fixed loading of Android exception handler when `Thread.contextClassLoader` is mocked (see #530).
+* Fixed bug when `IO` dispatcher silently hung (see #524 and #525) .
+
+## Version 0.25.3
+
+* Distribution no longer uses multi-version jar which is not supported on Android (see #510).
+* JS version of the library does not depend on AtomicFu anymore:
+  All the atomic boxes in JS are fully erased.
+* Note that versions 0.25.1-2 are skipped for technical reasons (they were not fully released).
+
+## 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.
+ * 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.
+ * Change `handleCoroutineException` contract, so custom exception handlers can't break coroutines machinery.
+ * Unwrap `JobCancellationException` properly to provide exception transparency over whole call chain.
+* 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.
+ * 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).
+* Built with Kotlin 1.2.61 and Kotlin/Native 0.8.2.
+* JAR files for `kotlinx-coroutines` are now [JEP 238](https://openjdk.java.net/jeps/238) multi-release JAR files.
+ * On JDK9+ `VarHandle` is used for atomic operations instead of `Atomic*FieldUpdater` for better performance.
+ * See [AtomicFu](https://github.com/Kotlin/kotlinx.atomicfu/blob/master/README.md) project for details.
+* Reversed addition of `BlockingChecker` extension point to control where `runBlocking` can be used (see #227).
+ * `runBlocking` can be used anywhere without limitations (again), but it would still cause problems if improperly used on UI thread.
+* Corrected return-type of `EventLoop` pseudo-constructor (see #477, PR by @Groostav).
+* Fixed `as*Future()` integration functions to catch all `Throwable` exceptions (see #469).
+* Fixed `runBlocking` cancellation (see #501).
+* Fixed races and timing bugs in `withTimeoutOrNull` (see #498).
+* Execute `EventLoop.invokeOnTimeout` in `DefaultDispatcher` to allow busy-wait loops inside `runBlocking` (see #479).
+* Removed `kotlinx-coroutines-io` module from the project, it has moved to [kotlinx-io](https://github.com/kotlin/kotlinx-io/).
+* Provide experimental API to create limited view of experimental dispatcher (see #475).
+* Various minor fixes by @LouisCAD, @Dmitry-Borodin.
+
+## Version 0.24.0
+
+* Fully multiplatform release with Kotlin/Native support (see #246):
+ * Only single-threaded operation inside `runBlocking` event loop is supported at this moment.
+ * See details on setting up build environment [here](native/README.md).
+* Improved channels:
+ * Introduced `SendChannel.invokeOnClose` (see #341).
+ * Make `close`, `cancel`, `isClosedForSend`, `isClosedForReceive` and `offer` linearizable with other operations (see #359).
+ * Fixed bug when send operation can be stuck in channel forever.
+ * Fixed broadcast channels on JS (see #412).
+* Provides `BlockingChecker` mechanism which checks current context (see #227).
+ * Attempts to use `runBlocking` from any supported UI thread (Android, JavaFx, Swing) will result in exception.
+* Android:
+ * Worked around Android bugs with zero-size ForkJoinPool initialization (see #432, #288).
+ * Introduced `UI.immediate` extension as performance-optimization to immediately execute tasks which are invoked from the UI thread (see #381).
+ * Use it only when absolutely needed. It breaks asynchrony of coroutines and may lead to surprising and unexpected results.
+* Fixed materialization of a `cause` exception for `Job` onCancelling handlers (see #436).
+* Fixed JavaFx `UI` on Java 9 (see #443).
+* Fixed and documented the order between cancellation handlers and continuation resume (see #415).
+* Fixed resumption of cancelled continuation (see #450).
+* Includes multiple fixes to documentation contributed by @paolop, @SahilLone, @rocketraman, @bdavisx, @mtopolnik, @Groostav.
+* Experimental coroutines scheduler preview (JVM only):
+ * Written from scratch and optimized for communicating coroutines.
+ * Performs significantly better than ForkJoinPool on coroutine benchmarks and for connected applications with [ktor](https://ktor.io).
+ * Supports automatic creating of new threads for blocking operations running on the same thread pool (with an eye on solving #79), but there is no stable public API for it just yet.
+ * For preview, run JVM with `-Dkotlinx.coroutines.scheduler` option. In this case `DefaultDispatcher` is set to new experimental scheduler instead of FJP-based `CommonPool`.
+ * Submit your feedback to issue #261.
+
+## Version 0.23.4
+
+* Recompiled with Kotlin 1.2.51 to solve broken metadata problem (see [KT-24944](https://youtrack.jetbrains.com/issue/KT-24944)).
+
+## Version 0.23.3
+
+* Kotlin 1.2.50.
+* JS: Moved to atomicfu version 0.10.3 that properly matches NPM & Kotlin/JS module names (see #396).
+* Improve source-code compatibility with previous (0.22.x) version of `openChannel().use { ... }` pattern by providing deprecated extension function `use` on `ReceiveChannel`.
+
+## Version 0.23.2
+
+* IO: fix joining and continuous writing byte array interference.
+
+## Version 0.23.1
+
+* JS: Fix dependencies in NPM: add "kotlinx-atomicfu" dependency (see #370).
+* Introduce `broadcast` coroutine builder (see #280):
+ * Support `BroadcastChannel.cancel` method to drop the buffer.
+ * Introduce `ReceiveChannel.broadcast()` extension.
+* Fixed a bunch of doc typos (PRs by @paolop).
+* Corrected previous version's release notes (PR by @ansman).
+
+## Version 0.23.0
+
+* Kotlin 1.2.41
+* **Coroutines core module is made mostly cross-platform for JVM and JS**:
+ * Migrate channels and related operators to common, so channels can be used from JS (see #201).
+ * Most of the code is shared between JVM and JS versions using cross-platform version of [AtomicFU](https://github.com/Kotlin/kotlinx.atomicfu) library.
+ * The recent version of Kotlin allows default parameters in common code (see #348).
+ * The project is built using Gradle 4.6.
+* **Breaking change**: `CancellableContinuation` is not a `Job` anymore (see #219):
+ * It does not affect casual users of `suspendCancellableCoroutine`, since all the typically used functions are still there.
+ * `CancellableContinuation.invokeOnCompletion` is deprecated now and its semantics had subtly changed:
+ * `invokeOnCancellation` is a replacement for `invokeOnCompletion` to install a handler.
+ * The handler is **not** invoked on `resume` which corresponds to the typical usage pattern.
+ * There is no need to check for `cont.isCancelled` in a typical handler code anymore (since handler is invoked only when continuation is cancelled).
+ * Multiple cancellation handlers cannot be installed.
+ * Cancellation handlers cannot be removed (disposed of) anymore.
+ * This change is designed to allow better performance of suspending cancellable functions:
+ * Now `CancellableContinuation` implementation has simpler state machine and is implemented more efficiently.
+ * Exception handling in `AbstractContinuation` (that implements `CancellableContinuation`) is now consistent:
+ * Always prefer exception thrown from coroutine as exceptional reason, add cancellation cause as suppressed exception.
+* **Big change**: Deprecate `CoroutineScope.coroutineContext`:
+ * It is replaced with top-level `coroutineContext` function from Kotlin standard library.
+* Improve `ReceiveChannel` operators implementations to guarantee closing of the source channels under all circumstances (see #279):
+ * `onCompletion` parameter added to `produce` and all other coroutine builders.
+ * Introduce `ReceiveChannel.consumes(): CompletionHandler` extension function.
+* Replace `SubscriptionReceiveChannel` with `ReceiveChannel` (see #283, PR by @deva666).
+ * `ReceiveChannel.use` extension is introduced to preserve source compatibility, but is deprecated.
+ * `consume` or `consumeEach` extensions should be used for channels.
+ * When writing operators, `produce(onCompletion=consumes()) { ... }` pattern shall be used (see #279 above).
+* JS: Kotlin is declared as peer dependency (see #339, #340, PR by @ansman).
+* Invoke exception handler for actor on cancellation even when channel was successfully closed, so exceptions thrown by actor are always reported (see #368).
+* Introduce `awaitAll` and `joinAll` for `Deferred` and `Job` lists correspondingly (see #171).
+* Unwrap `CompletionException` exception in `CompletionStage.await` slow-path to provide consistent results (see #375).
+* Add extension to `ExecutorService` to return `CloseableCoroutineDispatcher` (see #278, PR by @deva666).
+* Fail with proper message during build if JDK_16 is not set (see #291, PR by @venkatperi).
+* Allow negative timeouts in `delay`, `withTimeout` and `onTimeout` (see #310).
+* Fix a few bugs (leaks on cancellation) in `delay`:
+ * Invoke `clearTimeout` on cancellation in JSDispatcher.
+ * Remove delayed task on cancellation from internal data structure on JVM.
+* Introduce `ticker` function to create "ticker channels" (see #327):
+ * It provides analogue of RX `Observable.timer` for coroutine channels.
+ * It is currently supported on JVM only.
+* Add a test-helper class `TestCoroutineContext` (see #297, PR by @streetsofboston).
+ * It is currently supported on JVM only.
+ * Ticker channels (#327) are not yet compatible with it.
+* Implement a better way to set `CoroutineContext.DEBUG` value (see #316, PR by @dmytrodanylyk):
+ * Made `CoroutineContext.DEBUG_PROPERTY_NAME` constant public.
+ * Introduce public constants with `"on"`, `"off"`, `"auto"` values.
+* Introduce system property to control `CommonPool` parallelism (see #343):
+ * `CommonPool.DEFAULT_PARALLELISM_PROPERTY_NAME` constant is introduced with a value of "kotlinx.coroutines.default.parallelism".
+* Include package-list files into documentation site (see #290).
+* Fix various typos in docs (PRs by @paolop and @ArtsiomCh).
+
+## Version 0.22.5
+
+* JS: Fixed main file reference in [NPM package](https://www.npmjs.com/package/kotlinx-coroutines-core)
+* Added context argument to `Channel.filterNot` (PR by @jcornaz).
+* Implemented debug `toString` for channels (see #185).
+
+## Version 0.22.4
+
+* JS: Publish to NPM (see #229).
+* JS: Use node-style dispatcher on ReactNative (see #236).
+* [jdk8 integration](integration/kotlinx-coroutines-jdk8/README.md) improvements:
+ * Added conversion from `CompletionStage` to `Deferred` (see #262, PR by @jcornaz).
+ * Use fast path in `CompletionStage.await` and make it cancellable.
+
+## Version 0.22.3
+
+* Fixed `produce` builder to close the channel on completion instead of cancelling it, which lead to lost elements with buffered channels (see #256).
+* Don't use `ForkJoinPool` if there is a `SecurityManager` present to work around JNLP problems (see #216, PR by @NikolayMetchev).
+* JS: Check for undefined `window.addEventListener` when choosing default coroutine dispatcher (see #230, PR by @ScottPierce).
+* Update 3rd party dependencies:
+ * [kotlinx-coroutines-rx1](reactive/kotlinx-coroutines-rx1) to RxJava version `1.3.6`.
+ * [kotlinx-coroutines-rx2](reactive/kotlinx-coroutines-rx2) to RxJava version `2.1.9`.
+ * [kotlinx-coroutines-guava](integration/kotlinx-coroutines-guava) to Guava version `24.0-jre`.
+
+## Version 0.22.2
+
+* Android: Use @Keep annotation on AndroidExceptionPreHandler to fix the problem on Android with minification enabled (see #214).
+* Reactive: Added `awaitFirstOrDefault` and `awaitFirstOrNull` extensions (see #224, PR by @konrad-kaminski).
+* Core: Fixed `withTimeout` and `withTimeoutOrNull` that should not use equals on result (see #212, PR by @konrad-kaminski).
+* Core: Fixed hanged receive from a closed subscription of BroadcastChannel (see #226).
+* IO: fixed error propagation (see https://github.com/ktorio/ktor/issues/301).
+* Include common sources into sources jar file to work around KT-20971.
+* Fixed bugs in documentation due to MPP.
+
+## Version 0.22.1
+
+* Migrated to Kotlin 1.2.21.
+* Improved `actor` builder documentation (see #210) and fixed bugs in rendered documentation due to multiplatform.
+* Fixed `runBlocking` to properly support specified dispatchers (see #209).
+* Fixed data race in `Job` implementation (it was hanging at `LockFreeLinkedList.helpDelete` on certain stress tests).
+* `AbstractCoroutine.onCancellation` is invoked before cancellation handler that is set via `invokeOnCompletion`.
+* Ensure that `launch` handles uncaught exception before another coroutine that uses `join` on it resumes (see #208).
+
+## Version 0.22
+
+* Migrated to Kotlin 1.2.20.
+* Introduced stable public API for `AbstractCoroutine`:
+ * Implements `Job`, `Continuation`, and `CoroutineScope`.
+ * Has overridable `onStart`, `onCancellation`, `onCompleted` and `onCompletedExceptionally` functions.
+ * Reactive integration modules are now implemented using public API only.
+ * Notifies onXXX before all the installed handlers, so `launch` handles uncaught exceptions before "joining" coroutines wakeup (see #208).
+
+## Version 0.21.2
+
+* Fixed `openSubscription` extension for reactive `Publisher`/`Observable`/`Flowable` when used with `select { ... }` and added an optional `request` parameter to specify how many elements are requested from publisher in advance on subscription (see #197).
+* Simplified implementation of `Channel.flatMap` using `toChannel` function to work around Android 5.0 APK install SIGSEGV (see #205).
+
+## Version 0.21.1
+
+* Improved performance of coroutine dispatching (`DispatchTask` instance is no longer allocated).
+* Fixed `Job.cancel` and `CompletableDeferred.complete` to support cancelling/completing states and properly wait for their children to complete on join/await (see #199).
+* Fixed a bug in binary heap implementation (used internally by `delay`) which could have resulted in wrong delay time in rare circumstances.
+* Coroutines library for [Kotlin/JS](js/README.md):
+ * `Promise.asDeferred` immediately installs handlers to avoid "Unhandled promise rejection" warning.
+ * Use `window.postMessage` instead of `setTimeout` for coroutines inside the browser to avoid timeout throttling (see #194).
+ * Use custom queue in `Window.awaitAnimationFrame` to align all animations and reduce overhead.
+ * Introduced `Window.asCoroutineDispatcher()` extension function.
+
+## Version 0.21
+
+* Migrated to Kotlin 1.2.10.
+* Coroutines library for [Kotlin/JS](js/README.md) and [multiplatform projects](https://kotlinlang.org/docs/reference/multiplatform.html) (see #33):
+ * `launch` and `async` coroutine builders.
+ * `Job` and `Deferred` light-weight future with cancellation support.
+ * `delay` and `yield` top-level suspending functions.
+ * `await` extension for JS `Promise` and `asPromise`/`asDeferred` conversions.
+ * `promise` coroutine builder.
+ * `Job()` and `CompletableDeferred()` factories.
+ * Full support for parent-child coroutine hierarchies.
+ * `Window.awaitAnimationFrame` extension function.
+ * [Sample frontend Kotlin/JS application](js/example-frontend-js/README.md) with coroutine-driven animations.
+* `run` is deprecated and renamed to `withContext` (see #134).
+* `runBlocking` and `EventLoop` implementations optimized (see #190).
+
+## Version 0.20
+
+* Migrated to Kotlin 1.2.0.
+* Channels:
+ * Sequence-like `filter`, `map`, etc extensions on `ReceiveChannel` are introduced (see #88 by @fvasco and #69 by @konrad-kaminski).
+ * Introduced `ReceiveChannel.cancel` method.
+ * All operators on `ReceiveChannel` fully consume the original channel (`cancel` it when they are done) using a helper `consume` extension.
+ * Deprecated `ActorJob` and `ProducerJob`; `actor` now returns `SendChannel` and `produce` returns `ReceiveChannel` (see #127).
+ * `SendChannel.sendBlocking` extension method (see #157 by @@fvasco).
+* Parent-child relations between coroutines:
+ * Introduced an optional `parent` job parameter for all coroutine builders so that code with an explict parent `Job` is more natural.
+ * Added `parent` parameter to `CompletableDeferred` constructor.
+ * Introduced `Job.children` property.
+ * `Job.cancelChildren` is now an extension (member is deprecated and hidden).
+ * `Job.joinChildren` extension is introduced.
+ * Deprecated `Job.attachChild` as a error-prone API.
+ * Fixed StackOverflow when waiting for a lot of completed children that did not remove their handlers from the parent.
+* Use `java.util.ServiceLoader` to find default instances of `CoroutineExceptionHandler`.
+* Android UI integration:
+ * Use `Thread.getUncaughtExceptionPreHandler` to make sure that exceptions are logged before crash (see #148).
+ * Introduce `UI.awaitFrame` for animation; added sample coroutine-based animation application for Android [here](ui/kotlinx-coroutines-android/animation-app).
+ * Fixed `delay(Long.MAX_VALUE)` (see #161)
+* Added missing `DefaultDispatcher` on some reactive operators (see #174 by @fvasco)
+* 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).
+
+## Version 0.19.3
+
+* Fixed `send`/`openSubscription` race in `ArrayBroadcastChannel`.
+ This race lead to stalled (hanged) `send`/`receive` invocations.
+* Project build has been migrated to Gradle.
+
+## Version 0.19.2
+
+* Fixed `ArrayBroadcastChannel` receive of stale elements on `openSubscription`.
+ Only elements that are sent after invocation of `openSubscription` are received now.
+* Added a default value for `context` parameter to `rxFlowable` (see #146 by @PhilGlass).
+* Exception propagation logic from cancelled coroutines is adjusted (see #152):
+ * When cancelled coroutine crashes due to some other exception, this other exception becomes the cancellation reason
+ of the coroutine, while the original cancellation reason is suppressed.
+ * `UnexpectedCoroutineException` is no longer used to report those cases as is removed.
+ * This fixes a race between crash of CPU-consuming coroutine and cancellation which resulted in an unhandled exception
+ and lead to crashes on Android.
+* `run` uses cancelling state & propagates exceptions when cancelled (see #147):
+ * When coroutine that was switched into a different dispatcher using `run` is cancelled, the run invocation does not
+ complete immediately, but waits until the body completes.
+ * If the body completes with exception, then this exception is propagated.
+* No `Job` in `newSingleThreadContext` and `newFixedThreadPoolContext` anymore (see #149, #151):
+ * This resolves the common issue of using `run(ctx)` where ctx comes from either `newSingleThreadContext` or
+ `newFixedThreadPoolContext` invocation. They both used to return a combination of dispatcher + job,
+ and this job was overriding the parent job, thus preventing propagation of cancellation. Not anymore.
+ * `ThreadPoolDispatcher` class is now public and is the result type for both functions.
+ It has the `close` method to release the thread pool.
+
+## Version 0.19.1
+
+* Failed parent Job cancels all children jobs, then waits for them them.
+ This makes parent-child hierarchies easier to get working right without
+ having to use `try/catch` or other exception handlers.
+* Fixed a race in `ArrayBroadcastChannel` between `send` and `openChannel` invocations
+ (see #138).
+* Fixed quite a rare race in `runBlocking` that resulted in `AssertionError`.
+ Unfortunately, cannot write a reliable stress-test to reproduce it.
+* Updated Reactor support to leverage Bismuth release train
+ (contributed by @sdeleuze, see PR #141)
+
+## Version 0.19
+
+* This release is published to Maven Central.
+* `DefaultDispatcher` is introduced (see #136):
+ * `launch`, `async`, `produce`, `actor` and other integration-specific coroutine builders now use
+ `DefaultDispatcher` as the default value for their `context` parameter.
+ * 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
+ 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.
+ * `Job.attachChild` and `Job.cancelChildren` are introduced.
+ * `Job.join` now always checks cancellation status of invoker coroutine for predictable behavior when joining
+ failed child coroutine.
+ * `Job.cancelAndJoin` extension is introduced.
+ * `CoroutineContext.cancel` and `CoroutineContext.cancelChildren` extensions are introduced for convenience.
+ * `withTimeout`/`withTimeoutOrNull` blocks become proper coroutines that have `CoroutineScope` and wait for children, too.
+ * Diagnostics in cancellation and unexpected exception messages are improved,
+ coroutine name is included in debug mode.
+ * Fixed cancellable suspending functions to throw `CancellationException` (as was documented before) even when
+ the coroutine is cancelled with another application-specific exception.
+ * `JobCancellationException` is introduced as a specific subclass of `CancellationException` which is
+ used for coroutines that are cancelled without cause and to wrap application-specific exceptions.
+ * `Job.getCompletionException` is renamed to `Job.getCancellationException` and return a wrapper exception if needed.
+ * Introduced `Deferred.getCompletionExceptionOrNull` to get not-wrapped exception result of `async` task.
+ * Updated docs for `Job` & `Deferred` to explain parent/child relations.
+* `select` expression is modularized:
+ * `SelectClause(0,1,2)` interfaces are introduced, so that synchronization
+ constructs can define their select clauses without having to modify
+ the source of the `SelectBuilder` in `kotlinx-corounes-core` module.
+ * `Job.onJoin`, `Deferred.onAwait`, `Mutex.onLock`, `SendChannel.onSend`, `ReceiveChannel.onReceive`, etc
+ that were functions before are now properties returning the corresponding select clauses. Old functions
+ are left in bytecode for backwards compatibility on use-site, but any outside code that was implementing those
+ interfaces by itself must be updated.
+ * This opens road to moving channels into a separate module in future updates.
+* Renamed `TimeoutException` to `TimeoutCancellationException` (old name is deprecated).
+* Fixed various minor problems:
+ * JavaFx toolkit is now initialized by `JavaFx` context (see #108).
+ * Fixed lost ACC_STATIC on <clinit> methods (see #116).
+ * Fixed link to source code from documentation (see #129).
+ * Fixed `delay` in arbitrary contexts (see #133).
+* `kotlinx-coroutines-io` module is introduced. It is a work-in-progress on `ByteReadChannel` and `ByteWriteChannel`
+ interfaces, their implementations, and related classes to enable convenient coroutine integration with various
+ asynchronous I/O libraries and sockets. It is currently _unstable_ and **will change** in the next release.
+
+## Version 0.18
+
+* Kotlin 1.1.4 is required to use this version, which enables:
+ * `withLock` and `consumeEach` functions are now inline suspend functions.
+ * `JobSupport` class implementation is optimized (one fewer field).
+* `TimeoutException` is public (see #89).
+* Improvements to `Mutex` (courtesy of @fvasco):
+ * Introduced `holdsLock` (see #92).
+ * Improved documentation on `Mutex` fairness (see #90).
+* Fixed NPE when `ArrayBroadcastChannel` is closed concurrently with receive (see #97).
+* Fixed bug in internal class LockFreeLinkedList that resulted in ISE under stress in extremely rare circumstances.
+* Integrations:
+ * [quasar](integration/kotlinx-coroutines-quasar): Introduced integration with suspendable JVM functions
+ that are instrumented with [Parallel Universe Quasar](https://docs.paralleluniverse.co/quasar/)
+ (thanks to the help of @pron).
+ * [reactor](reactive/kotlinx-coroutines-reactor): Replaced deprecated `setCancellation` with `onDipose` and
+ updated to Aluminium-SR3 release (courtesy of @yxf07, see #96)
+ * [jdk8](integration/kotlinx-coroutines-jdk8): Added adapters for `java.time` classes (courtesy of @fvasco, see #93)
+
+## 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.
+ * `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).
+ * `Job` and `Deferred` have a new _cancelling_ state which they enter on invocation of `cancel`.
+ * `Job.invokeOnCompletion` has an additional overload with `onCancelling: Boolean` parameter to
+ install handlers that are fired as soon as coroutine enters _cancelling_ state as opposed
+ to waiting until it _completes_.
+ * Internal `select` implementation is refactored to decouple it from `JobSupport` internal class
+ and to optimize its state-machine.
+ * Internal `AbstractCoroutine` class is refactored so that it is extended only by true coroutines,
+ all of which support the new _cancelling_ state.
+* `CoroutineScope.context` is renamed to `coroutineContext` to avoid conflicts with other usages of `context`
+ in applications (like Android context, see #75).
+* `BroadcastChannel.open` is renamed to `openSubscription` (see #54).
+* Fixed `StackOverflowError` in a convoy of `Mutex.unlock` invokers with `Unconfined` dispatcher (see #80).
+* Fixed `SecurityException` when trying to use coroutines library with installed `SecurityManager`.
+* Fixed a bug in `withTimeoutOrNull` in case with nested timeouts when coroutine was cancelled before it was
+ ever suspended.
+* Fixed a minor problem with `awaitFirst` on reactive streams that would have resulted in spurious stack-traces printed
+ on the console when used with publishers/observables that continue to invoke `onNext` despite being cancelled/disposed
+ (which they are technically allowed to do by specification).
+* All factory functions for various interfaces are implemented as top-level functions
+ (affects `Job`, `Channel`, `BroadcastChannel`, `Mutex`, `EventLoop`, and `CoroutineExceptionHandler`).
+ Previous approach of using `operator invoke` on their companion objects is deprecated.
+* Nicer-to-use debug `toString` implementations for coroutine dispatcher tasks and continuations.
+* A default dispatcher for `delay` is rewritten and now shares code with `EventLoopImpl` that is used by
+ `runBlocking`. It internally supports non-default `TimeSource` so that delay-using tests can be written
+ with "virtual time" by replacing their time source for the duration of tests (this feature is not available
+ outside of the library).
+
+## Version 0.16
+
+* Coroutines that are scheduled for execution are cancellable by default now
+ * `suspendAtomicCancellableCoroutine` function is introduced for funs like
+  `send`/`receive`/`receiveOrNull` that require atomic cancellation
+  (they cannot be cancelled after decision was made)
+ * Coroutines started with default mode using
+  `async`/`launch`/`actor` builders can be cancelled before their execution starts
+ * `CoroutineStart.ATOMIC` is introduced as a start mode to specify that
+  coroutine cannot be cancelled before its execution starts
+ * `run` function is also cancellable in the same way and accepts an optional
+ `CoroutineStart` parameter to change this default.
+* `BroadcastChannel` factory function is introduced
+* `CoroutineExceptionHandler` factory function is introduced by @konrad-kaminski
+* [`integration`](integration) directory is introduced for all 3rd party integration projects
+ * It has [contribution guidelines](integration/README.md#contributing) and contributions from community are welcome
+ * Support for Guava `ListenableFuture` in the new [`kotlinx-coroutines-guava`](integration/kotlinx-coroutines-guava) module
+ * Rx1 Scheduler support by @konrad-kaminski
+* Fixed a number of `Channel` and `BroadcastChannel` implementation bugs related to concurrent
+ send/close/close of channels that lead to hanging send, offer or close operations (see #66).
+ Thanks to @chrisly42 and @cy6erGn0m for finding them.
+* Fixed `withTimeoutOrNull` which was returning `null` on timeout of inner or outer `withTimeout` blocks (see #67).
+ Thanks to @gregschlom for finding the problem.
+* Fixed a bug where `Job` fails to dispose a handler when it is the only handler by @uchuhimo
+
+## Version 0.15
+
+* Switched to Kotlin version 1.1.2 (can still be used with 1.1.0).
+* `CoroutineStart` enum is introduced for `launch`/`async`/`actor` builders:
+ * The usage of `luanch(context, start = false)` is deprecated and is replaced with
+ `launch(context, CoroutineStart.LAZY)`
+ * `CoroutineStart.UNDISPATCHED` is introduced to start coroutine execution immediately in the invoker thread,
+ so that `async(context, CoroutineStart.UNDISPATCHED)` is similar to the behavior of C# `async`.
+ * [Guide to UI programming with coroutines](ui/coroutines-guide-ui.md) mentions the use of it to optimize
+ the start of coroutines from UI threads.
+* Introduced `BroadcastChannel` interface in `kotlinx-coroutines-core` module:
+ * It extends `SendChannel` interface and provides `open` function to create subscriptions.
+ * Subscriptions are represented with `SubscriptionReceiveChannel` interface.
+ * The corresponding `SubscriptionReceiveChannel` interfaces are removed from [reactive](reactive) implementation
+ modules. They use an interface defined in `kotlinx-coroutines-core` module.
+ * `ConflatedBroadcastChannel` implementation is provided for state-observation-like use-cases, where a coroutine or a
+ regular code (in UI, for example) updates the state that subscriber coroutines shall react to.
+ * `ArrayBroadcastChannel` implementation is provided for event-bus-like use-cases, where a sequence of events shall
+ be received by multiple subscribers without any omissions.
+ * [Guide to reactive streams with coroutines](reactive/coroutines-guide-reactive.md) includes
+ "Rx Subject vs BroadcastChannel" section.
+* Pull requests from Konrad Kamiński are merged into reactive stream implementations:
+ * Support for Project Reactor `Mono` and `Flux`.
+ See [`kotlinx-coroutines-reactor`](reactive/kotlinx-coroutines-reactor) module.
+ * Implemented Rx1 `Completable.awaitCompleted`.
+ * Added support for Rx2 `Maybe`.
+* Better timeout support:
+ * Introduced `withTimeoutOrNull` function.
+ * Implemented `onTimeout` clause for `select` expressions.
+ * Fixed spurious concurrency inside `withTimeout` blocks on their cancellation.
+ * Changed behavior of `withTimeout` when `CancellationException` is suppressed inside the block.
+ Invocation of `withTimeout` now always returns the result of execution of its inner block.
+* The `channel` property in `ActorScope` is promoted to a wider `Channel` type, so that an actor
+ can have an easy access to its own inbox send channel.
+* Renamed `Mutex.withMutex` to `Mutex.withLock`, old name is deprecated.
+
+## Version 0.14
+
+* Switched to Kotlin version 1.1.1 (can still be used with 1.1.0).
+* Introduced `consumeEach` helper function for channels and reactive streams, Rx 1.x, and Rx 2.x.
+ * It ensures that streams are unsubscribed from on any exception.
+ * Iteration with `for` loop on reactive streams is **deprecated**.
+ * [Guide to reactive streams with coroutines](reactive/coroutines-guide-reactive.md) is updated virtually
+ all over the place to reflect these important changes.
+* Implemented `awaitFirstOrDefault` extension for reactive streams, Rx 1.x, and Rx 2.x.
+* Added `Mutex.withMutex` helper function.
+* `kotlinx-coroutines-android` module has `provided` dependency on of Android APIs to
+ eliminate warnings when using it in android project.
+
+## Version 0.13
+
+* New `kotlinx-coroutinex-android` module with Android `UI` context implementation.
+* Introduced `whileSelect` convenience function.
+* Implemented `ConflatedChannel`.
+* Renamed various `toXXX` conversion functions to `asXXX` (old names are deprecated).
+* `run` is optimized with fast-path case and no longer has `CoroutineScope` in its block.
+* Fixed dispatching logic of `withTimeout` (removed extra dispatch).
+* `EventLoop` that is used by `runBlocking` now implements Delay, giving more predictable test behavior.
+* Various refactorings related to resource management and timeouts:
+ * `Job.Registration` is renamed to `DisposableHandle`.
+ * `EmptyRegistration` is renamed to `NonDisposableHandle`.
+ * `Job.unregisterOnCompletion` is renamed to `Job.disposeOnCompletion`.
+ * `Delay.invokeOnTimeout` is introduced.
+ * `withTimeout` now uses `Delay.invokeOnTimeout` when available.
+* A number of improvement for reactive streams and Rx:
+ * Introduced `rxFlowable` builder for Rx 2.x.
+ * `Scheduler.asCoroutineDispatcher` extension for Rx 2.x.
+ * Fixed bug with sometimes missing `onComplete` in `publish`, `rxObservable`, and `rxFlowable` builders.
+ * Channels that are open for reactive streams are now `Closeable`.
+ * Fixed `CompletableSource.await` and added test for it.
+ * Removed `rx.Completable.await` due to name conflict.
+* New documentation:
+ * [Guide to UI programming with coroutines](ui/coroutines-guide-ui.md)
+ * [Guide to reactive streams with coroutines](reactive/coroutines-guide-reactive.md)
+* Code is published to JCenter repository.
+
+## Version 0.12
+
+* Switched to Kotlin version 1.1.0 release.
+* Reworked and updated utilities for
+ [Reactive Streams](kotlinx-coroutines-reactive),
+ [Rx 1.x](kotlinx-coroutines-rx1), and
+ [Rx 2.x](kotlinx-coroutines-rx2) with library-specific
+ coroutine builders, suspending functions, converters and iteration support.
+* `LinkedListChannel` with unlimited buffer (`offer` always succeeds).
+* `onLock` select clause and an optional `owner` parameter in all `Mutex` functions.
+* `selectUnbiased` function.
+* `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).
+
+## Version 0.11-rc
+
+* `select` expression with onJoin/onAwait/onSend/onReceive clauses.
+* `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).
+
+## Version 0.10-rc
+
+* Switched to Kotlin version 1.1.0-rc-91.
+* `Mutex` synchronization primitive is introduced.
+* `buildChannel` is renamed to `produce`, old name is deprecated.
+* `Job.onCompletion` is renamed to `Job.invokeOnCompletion`, old name is deprecated.
+* `delay` implementation in Swing, JavaFx, and scheduled executors is fixed to avoid an extra dispatch.
+* `CancellableContinuation.resumeUndispatched` is introduced to make this efficient implementation possible.
+* Remove unnecessary creation of `CancellationException` to improve performance, plus other performance improvements.
+* Suppress deprecated and internal APIs from docs.
+* Better docs at top level with categorized summary of classes and functions.
+
+## Version 0.8-beta
+
+* `defer` coroutine builder is renamed to `async`.
+* `lazyDefer` is deprecated, `async` has an optional `start` parameter instead.
+* `LazyDeferred` interface is deprecated, lazy start functionality is integrated into `Job` interface.
+* `launch` has an optional `start` parameter for lazily started coroutines.
+* `Job.start` and `Job.isCompleted` are introduced.
+* `Deferred.isCompletedExceptionally` and `Deferred.isCancelled` are introduced.
+* `Job.getInactiveCancellationException` is renamed to `getCompletionException`.
+* `Job.join` is now a member function.
+* Internal `JobSupport` state machine is enhanced to support _new_ (not-started-yet) state.
+ 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).
+
+## Version 0.7-beta
+
+* Buffered and unbuffered channels are introduced: `Channel`, `SendChannel`, and `ReceiveChannel` interfaces,
+ `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.
+
+## Version 0.6-beta
+
+* Switched to Kotlin version 1.1.0-beta-37.
+* A [guide on coroutines](docs/coroutines-guide.md) is expanded.
+
+## Version 0.5-beta
+
+* Switched to Kotlin version 1.1.0-beta-22 (republished version).
+* Removed `currentCoroutineContext` and related thread-locals without replacement.
+ Explicitly pass coroutine context around if needed.
+* `lazyDefer(context) {...}` coroutine builder and `LazyDeferred` interface are introduced.
+* The default behaviour of all coroutine dispatchers is changed to always schedule execution of new coroutine
+ for later in this thread or thread pool. Correspondingly, `CoroutineDispatcher.isDispatchNeeded` function
+ 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.
+
+## Version 0.4-beta
+
+* Switched to Kotlin version 1.1.0-beta-18 (republished version).
+* `CoroutineDispatcher` methods now have `context` parameter.
+* Introduced `CancellableContinuation.isCancelled`
+* Introduced `EventLoop` dispatcher and made it a default for `runBlocking { ... }`
+* Introduced `CoroutineScope` interface with `isActive` and `context` properties;
+ standard coroutine builders include it as receiver for convenience.
+* Introduced `Executor.toCoroutineDispatcher()` extension.
+* Delay scheduler thread is not daemon anymore, but times out automatically.
+* Debugging facilities in `newCoroutineContext` can be explicitly disabled with `-Dkotlinx.coroutines.debug=off`.
+* xxx-test files are renamed to xxx-example for clarity.
+* Fixed NPE in Job implementation when starting coroutine with already cancelled parent job.
+* Support cancellation in `kotlinx-coroutines-nio` module
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..85ed20db
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,4 @@
+## Code of Conduct
+
+This project and the corresponding community is governed by the [JetBrains Open Source and Community Code of Conduct](https://confluence.jetbrains.com/display/ALL/JetBrains+Open+Source+and+Community+Code+of+Conduct). Please make sure you read it.
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..dead69c1
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,13 @@
+Copyright 2016-2019 JetBrains s.r.o.
+
+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
+
+http://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. \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..e31d6f24
--- /dev/null
+++ b/README.md
@@ -0,0 +1,291 @@
+# kotlinx.coroutines
+
+[![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.3.1) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.3.1)
+
+Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
+This is a companion version for Kotlin `1.3.50` release.
+
+```kotlin
+suspend fun main() = coroutineScope {
+ launch {
+ delay(1000)
+ println("Kotlin Coroutines World!")
+ }
+ println("Hello")
+}
+```
+
+> Play with coroutines online [here](https://pl.kotl.in/hG_tKbid_)
+
+## Modules
+
+* [core](kotlinx-coroutines-core/README.md) &mdash; common coroutines across all platforms:
+ * [launch] and [async] coroutine builders returning [Job] and [Deferred] light-weight futures with cancellation support;
+ * [Dispatchers] object with [Main][Dispatchers.Main] dispatcher for Android/Swing/JavaFx, and [Default][Dispatchers.Default] dispatcher for background coroutines;
+ * [delay] and [yield] top-level suspending functions;
+ * [Flow] &mdash; cold asynchronous stream with [flow][_flow] builder and comprehensive operator set ([filter], [map], etc);
+ * [Channel], [Mutex], and [Semaphore] communication and synchronization primitives;
+ * [coroutineScope], [supervisorScope], [withContext], and [withTimeout] scope builders;
+ * [MainScope()] for Android and UI applications;
+ * [SupervisorJob()] and [CoroutineExceptionHandler] for supervision of coroutines hierarchies;
+ * [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.
+* [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.
+* [test](kotlinx-coroutines-test/README.md) &mdash; test utilities for coroutines:
+ * [Dispatchers.setMain] to override [Dispatchers.Main] in tests;
+ * [TestCoroutineScope] to test suspending functions and coroutines.
+* [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.
+* [reactive](reactive/README.md) &mdash; modules that provide builders and iteration support for various reactive streams libraries:
+ * Reactive Streams ([Publisher.collect], [Publisher.awaitSingle], [publish], etc),
+ RxJava 2.x ([rxFlowable], [rxSingle], etc), and
+ Project Reactor ([flux], [mono], etc).
+* [ui](ui/README.md) &mdash; modules that provide coroutine dispatchers for various single-threaded UI libraries:
+ * Android, JavaFX, and Swing.
+* [integration](integration/README.md) &mdash; modules that provide integration with various asynchronous callback- and future-based libraries:
+ * JDK8 [CompletionStage.await], Guava [ListenableFuture.await], and Google Play Services [Task.await];
+ * SLF4J MDC integration via [MDCContext].
+
+## Documentation
+
+* Presentations and videos:
+ * [Introduction to Coroutines](https://www.youtube.com/watch?v=_hfBv0a09Jc) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/introduction-to-coroutines-kotlinconf-2017))
+ * [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 UI programming with coroutines](ui/coroutines-guide-ui.md)
+ * [Guide to reactive streams with coroutines](reactive/coroutines-guide-reactive.md)
+ * [Debugging capabilities in kotlinx.coroutines](docs/debugging.md)
+* [Compatibility policy and experimental annotations](docs/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):
+
+```xml
+<dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-coroutines-core</artifactId>
+ <version>1.3.1</version>
+</dependency>
+```
+
+And make sure that you use the latest Kotlin version:
+
+```xml
+<properties>
+ <kotlin.version>1.3.50</kotlin.version>
+</properties>
+```
+
+### Gradle
+
+Add dependencies (you can also add other modules that you need):
+
+```groovy
+dependencies {
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1'
+}
+```
+
+And make sure that you use the latest Kotlin version:
+
+```groovy
+buildscript {
+ ext.kotlin_version = '1.3.50'
+}
+```
+
+Make sure that you have either `jcenter()` or `mavenCentral()` in the list of repositories:
+
+```
+repository {
+ jcenter()
+}
+```
+
+### Gradle Kotlin DSL
+
+Add dependencies (you can also add other modules that you need):
+
+```groovy
+dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1")
+}
+```
+
+And make sure that you use the latest Kotlin version:
+
+```groovy
+plugins {
+ kotlin("jvm") version "1.3.50"
+}
+```
+
+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, add dependency to
+[`kotlinx-coroutines-core-common`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-common/1.3.1/jar)
+(follow the link to get the dependency declaration snippet).
+
+### Android
+
+Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
+module as dependency when using `kotlinx.coroutines` on Android:
+
+```groovy
+implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1'
+```
+
+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.
+
+#### R8 and ProGuard
+
+For R8 no actions required, it will take obfuscation rules from the jar.
+
+For Proguard you need to add options from [coroutines.pro](kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro) to your rules manually.
+
+R8 is a replacement for ProGuard in Android ecosystem, it is enabled by default since Android gradle plugin 3.4.0 (3.3.0-beta also had it enabled).
+
+### JS
+
+[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.3.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.
+
+### Native
+
+[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.3.1/jar)
+(follow the link to get the dependency declaration snippet).
+
+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:
+
+```groovy
+enableFeaturePreview('GRADLE_METADATA')
+```
+
+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`.
+
+## Building
+
+This library is built with Gradle. To build it, use `./gradlew build`.
+You can import this project into IDEA, but you have to delegate build actions
+to Gradle (in Preferences -> Build, Execution, Deployment -> Build Tools -> Gradle -> Runner)
+
+### Requirements
+
+* JDK >= 1.8 referred to by the `JAVA_HOME` environment variable.
+* JDK 1.6 referred to by the `JDK_16` environment variable. It is okay to have `JDK_16` pointing to `JAVA_HOME` for external contributions.
+
+## Contributions and releases
+
+All development (both new features and bug fixes) is performed in `develop` branch.
+This way `master` sources always contain sources of the most recently released version.
+Please send PRs with bug fixes to `develop` branch.
+Fixes to documentation in markdown files are an exception to this rule. They are updated directly in `master`.
+
+The `develop` branch is pushed to `master` during release.
+
+* Full release procedure checklist is [here](RELEASE.md).
+* Steps for contributing new integration modules are explained [here](integration/README.md#Contributing).
+
+<!--- 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
+[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[Dispatchers]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/index.html
+[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
+[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.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
+[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
+[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
+[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
+[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
+[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
+<!--- 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
+[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
+<!--- 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
+<!--- 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
+<!--- 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
+[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-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
new file mode 100644
index 00000000..22140e68
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,80 @@
+# kotlinx.coroutines release checklist
+
+To release new `<version>` of `kotlinx-coroutines`:
+
+1. Checkout `develop` branch: <br>
+ `git checkout develop`
+
+2. Retrieve the most recent `develop`: <br>
+ `git pull`
+
+3. Make sure the `master` branch is fully merged into `develop`:
+ `git merge origin/master`
+
+4. Search & replace `<old-version>` with `<version>` across the project files. Should replace in:
+ * [`README.md`](README.md) (native, core, test, debug, modules)
+ * [`coroutines-guide.md`](docs/coroutines-guide.md)
+ * [`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)
+ * Make sure to **exclude** `CHANGES.md` from replacements.
+
+ As an alternative approach you can use `./bump-version.sh old_version new_version`
+
+5. Write release notes in [`CHANGES.md`](CHANGES.md):
+ * Use old releases as example of style.
+ * Write each change on a single line (don't wrap with CR).
+ * Study commit message from previous release.
+
+6. Create branch for this release:
+ `git checkout -b version-<version>`
+
+7. Commit updated files to a new version branch:<br>
+ `git commit -a -m "Version <version>"`
+
+8. Push new version into the branch:<br>
+ `git push -u origin version-<version>`
+
+9. Create Pull-Request on GitHub from `version-<version>` branch into `master`:
+ * Review it.
+ * Make sure it build on CI.
+ * Get approval for it.
+
+0. Merge new version branch into `master`:<br>
+ `git checkout master`<br>
+ `git merge version-<version>`<br>
+ `git push`
+
+1. On [TeamCity integration server](https://teamcity.jetbrains.com/project.html?projectId=KotlinTools_KotlinxCoroutines):
+ * Wait until "Build" configuration for committed `master` branch passes tests.
+ * Run "Deploy (Configure, RUN THIS ONE)" configuration with the corresponding new version.
+
+2. In [GitHub](https://github.com/kotlin/kotlinx.coroutines) interface:
+ * Create a release named `<version>`.
+ * Cut & paste lines from [`CHANGES.md`](CHANGES.md) into description.
+
+3. Build and publish documentation for web-site: <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.
+
+5. Announce new release in [Slack](https://kotlinlang.slack.com)
+
+6. Create a ticket to update coroutines version on [try.kotlinlang.org](try.kotlinlang.org).
+ * Use [KT-30870](https://youtrack.jetbrains.com/issue/KT-30870) as a template
+ * This step should be skipped for eap versions that are not merged to `master`
+
+7. Switch into `develop` branch:<br>
+ `git checkout develop`
+
+8. Fetch the latest `master`:<br>
+ `git fetch`
+
+9. Merge release from `master`:<br>
+ `git merge origin/master`
+
+0. Push updates to `develop`:<br>
+ `git push`
diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle
new file mode 100644
index 00000000..157eb88a
--- /dev/null
+++ b/benchmarks/build.gradle
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+apply plugin: "net.ltgt.apt"
+apply plugin: "com.github.johnrengelman.shadow"
+apply plugin: "me.champeau.gradle.jmh"
+
+repositories {
+ maven { url "https://repo.typesafe.com/typesafe/releases/" }
+}
+
+compileJmhKotlin {
+ kotlinOptions {
+ jvmTarget = "1.8"
+ freeCompilerArgs += ['-Xjvm-default=enable']
+ }
+}
+
+/*
+ * Due to a bug in the inliner it sometimes does not remove inlined symbols (that are later renamed) from unused code paths,
+ * and it breaks JMH that tries to post-process these symbols and fails because they are renamed.
+ */
+task removeRedundantFiles(type: Delete) {
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$nBlanks\$1\$\$special\$\$inlined\$map\$1\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$score2\$1\$\$special\$\$inlined\$map\$1\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$bonusForDoubleLetter\$1\$\$special\$\$inlined\$map\$1\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$nBlanks\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$bonusForDoubleLetter\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$score2\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOptKt\$\$special\$\$inlined\$collect\$1\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOptKt\$\$special\$\$inlined\$collect\$2\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble//SaneFlowPlaysScrabble\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
+
+ // Primes
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/misc/Numbers\$\$special\$\$inlined\$filter\$1\$2\$1.class"
+ delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/misc/Numbers\$\$special\$\$inlined\$filter\$1\$1.class"
+}
+
+jmhRunBytecodeGenerator.dependsOn(removeRedundantFiles)
+
+// It is better to use the following to run benchmarks, otherwise you may get unexpected errors:
+// ./gradlew --no-daemon cleanJmhJar jmh -Pjmh="MyBenchmark"
+jmh {
+ jmhVersion = '1.21'
+ duplicateClassesStrategy DuplicatesStrategy.INCLUDE
+ failOnError = true
+ resultFormat = 'CSV'
+ if (project.hasProperty('jmh')) {
+ include = ".*" + project.jmh + ".*"
+ }
+// includeTests = false
+}
+
+jmhJar {
+ baseName 'benchmarks'
+ classifier = null
+ version = null
+ destinationDir = file("$rootDir")
+}
+
+dependencies {
+ compile "org.openjdk.jmh:jmh-core:1.21"
+ compile "io.projectreactor:reactor-core:$reactor_vesion"
+ compile 'io.reactivex.rxjava2:rxjava:2.1.9'
+ compile "com.github.akarnokd:rxjava2-extensions:0.20.8"
+
+ compile "org.openjdk.jmh:jmh-core:1.21"
+ compile 'com.typesafe.akka:akka-actor_2.12:2.5.0'
+ compile project(':kotlinx-coroutines-core')
+}
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java
new file mode 100644
index 00000000..2a85d0db
--- /dev/null
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble;
+
+import benchmarks.flow.scrabble.IterableSpliterator;
+import benchmarks.flow.scrabble.ShakespearePlaysScrabble;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.functions.Function;
+import org.openjdk.jmh.annotations.*;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Shakespeare plays Scrabble with RxJava 2 Flowable.
+ * @author José
+ * @author akarnokd
+ */
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+public class RxJava2PlaysScrabble extends ShakespearePlaysScrabble {
+
+ @Benchmark
+ @Override
+ public List<Entry<Integer, List<String>>> play() throws Exception {
+
+ // Function to compute the score of a given word
+ Function<Integer, Flowable<Integer>> scoreOfALetter = letter -> Flowable.just(letterScores[letter - 'a']) ;
+
+ // score of the same letters in a word
+ Function<Entry<Integer, LongWrapper>, Flowable<Integer>> letterScore =
+ entry ->
+ Flowable.just(
+ letterScores[entry.getKey() - 'a'] *
+ Integer.min(
+ (int)entry.getValue().get(),
+ scrabbleAvailableLetters[entry.getKey() - 'a']
+ )
+ ) ;
+
+ Function<String, Flowable<Integer>> toIntegerFlowable =
+ string -> Flowable.fromIterable(IterableSpliterator.of(string.chars().boxed().spliterator())) ;
+
+ // Histogram of the letters in a given word
+ Function<String, Single<HashMap<Integer, LongWrapper>>> histoOfLetters =
+ word -> toIntegerFlowable.apply(word)
+ .collect(
+ () -> new HashMap<>(),
+ (HashMap<Integer, LongWrapper> map, Integer value) ->
+ {
+ LongWrapper newValue = map.get(value) ;
+ if (newValue == null) {
+ newValue = () -> 0L ;
+ }
+ map.put(value, newValue.incAndSet()) ;
+ }
+
+ ) ;
+
+ // number of blanks for a given letter
+ Function<Entry<Integer, LongWrapper>, Flowable<Long>> blank =
+ entry ->
+ Flowable.just(
+ Long.max(
+ 0L,
+ entry.getValue().get() -
+ scrabbleAvailableLetters[entry.getKey() - 'a']
+ )
+ ) ;
+
+ // number of blanks for a given word
+ Function<String, Maybe<Long>> nBlanks =
+ word -> histoOfLetters.apply(word)
+ .flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
+ .flatMap(blank)
+ .reduce(Long::sum) ;
+
+
+ // can a word be written with 2 blanks?
+ Function<String, Maybe<Boolean>> checkBlanks =
+ word -> nBlanks.apply(word)
+ .flatMap(l -> Maybe.just(l <= 2L)) ;
+
+ // score taking blanks into account letterScore1
+ Function<String, Maybe<Integer>> score2 =
+ word -> histoOfLetters.apply(word)
+ .flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
+ .flatMap(letterScore)
+ .reduce(Integer::sum) ;
+
+ // Placing the word on the board
+ // Building the streams of first and last letters
+ Function<String, Flowable<Integer>> first3 =
+ word -> Flowable.fromIterable(IterableSpliterator.of(word.chars().boxed().limit(3).spliterator())) ;
+ Function<String, Flowable<Integer>> last3 =
+ word -> Flowable.fromIterable(IterableSpliterator.of(word.chars().boxed().skip(3).spliterator())) ;
+
+
+ // Stream to be maxed
+ Function<String, Flowable<Integer>> toBeMaxed =
+ word -> Flowable.just(first3.apply(word), last3.apply(word))
+ .flatMap(observable -> observable) ;
+
+ // Bonus for double letter
+ Function<String, Maybe<Integer>> bonusForDoubleLetter =
+ word -> toBeMaxed.apply(word)
+ .flatMap(scoreOfALetter)
+ .reduce(Integer::max) ;
+
+ // score of the word put on the board
+ Function<String, Maybe<Integer>> score3 =
+ word ->
+ Maybe.merge(Arrays.asList(
+ score2.apply(word),
+ score2.apply(word),
+ bonusForDoubleLetter.apply(word),
+ bonusForDoubleLetter.apply(word),
+ Maybe.just(word.length() == 7 ? 50 : 0)
+ )
+ )
+ .reduce(Integer::sum) ;
+
+ Function<Function<String, Maybe<Integer>>, Single<TreeMap<Integer, List<String>>>> buildHistoOnScore =
+ score -> Flowable.fromIterable(() -> shakespeareWords.iterator())
+ .filter(scrabbleWords::contains)
+ .filter(word -> checkBlanks.apply(word).blockingGet())
+ .collect(
+ () -> new TreeMap<>(Comparator.reverseOrder()),
+ (TreeMap<Integer, List<String>> map, String word) -> {
+ Integer key = score.apply(word).blockingGet() ;
+ List<String> list = map.get(key) ;
+ if (list == null) {
+ list = new ArrayList<>() ;
+ map.put(key, list) ;
+ }
+ list.add(word) ;
+ }
+ ) ;
+
+ // best key / value pairs
+ List<Entry<Integer, List<String>>> finalList2 =
+ buildHistoOnScore.apply(score3)
+ .flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
+ .take(3)
+ .collect(
+ () -> new ArrayList<Entry<Integer, List<String>>>(),
+ (list, entry) -> {
+ list.add(entry) ;
+ }
+ )
+ .blockingGet() ;
+ return finalList2 ;
+ }
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java
new file mode 100644
index 00000000..7a7cb1aa
--- /dev/null
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+import hu.akarnokd.rxjava2.math.MathFlowable;
+import org.openjdk.jmh.annotations.*;
+import benchmarks.flow.scrabble.optimizations.*;
+import io.reactivex.*;
+import io.reactivex.functions.Function;
+
+/**
+ * Shakespeare plays Scrabble with RxJava 2 Flowable optimized.
+ * @author José
+ * @author akarnokd
+ */
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+public class RxJava2PlaysScrabbleOpt extends ShakespearePlaysScrabble {
+ static Flowable<Integer> chars(String word) {
+// return Flowable.range(0, word.length()).map(i -> (int)word.charAt(i));
+ return StringFlowable.characters(word);
+ }
+
+ @Benchmark
+ @Override
+ public List<Entry<Integer, List<String>>> play() throws Exception {
+
+ // to compute the score of a given word
+ Function<Integer, Integer> scoreOfALetter = letter -> letterScores[letter - 'a'];
+
+ // score of the same letters in a word
+ Function<Entry<Integer, MutableLong>, Integer> letterScore =
+ entry ->
+ letterScores[entry.getKey() - 'a'] *
+ Integer.min(
+ (int)entry.getValue().get(),
+ scrabbleAvailableLetters[entry.getKey() - 'a']
+ )
+ ;
+
+
+ Function<String, Flowable<Integer>> toIntegerFlowable =
+ string -> chars(string);
+
+ Map<String, Single<HashMap<Integer, MutableLong>>> histoCache = new HashMap<>();
+ // Histogram of the letters in a given word
+ Function<String, Single<HashMap<Integer, MutableLong>>> histoOfLetters =
+ word -> { Single<HashMap<Integer, MutableLong>> s = histoCache.get(word);
+ if (s == null) {
+ s = toIntegerFlowable.apply(word)
+ .collect(
+ () -> new HashMap<>(),
+ (HashMap<Integer, MutableLong> map, Integer value) ->
+ {
+ MutableLong newValue = map.get(value) ;
+ if (newValue == null) {
+ newValue = new MutableLong();
+ map.put(value, newValue);
+ }
+ newValue.incAndSet();
+ }
+
+ );
+ histoCache.put(word, s);
+ }
+ return s;
+ };
+
+ // number of blanks for a given letter
+ Function<Entry<Integer, MutableLong>, Long> blank =
+ entry ->
+ Long.max(
+ 0L,
+ entry.getValue().get() -
+ scrabbleAvailableLetters[entry.getKey() - 'a']
+ )
+ ;
+
+ // number of blanks for a given word
+ Function<String, Flowable<Long>> nBlanks =
+ word -> MathFlowable.sumLong(
+ histoOfLetters.apply(word).flattenAsFlowable(
+ map -> map.entrySet()
+ )
+ .map(blank)
+ )
+ ;
+
+
+ // can a word be written with 2 blanks?
+ Function<String, Flowable<Boolean>> checkBlanks =
+ word -> nBlanks.apply(word)
+ .map(l -> l <= 2L) ;
+
+ // score taking blanks into account letterScore1
+ Function<String, Flowable<Integer>> score2 =
+ word -> MathFlowable.sumInt(
+ histoOfLetters.apply(word).flattenAsFlowable(
+ map -> map.entrySet()
+ )
+ .map(letterScore)
+ ) ;
+
+ // Placing the word on the board
+ // Building the streams of first and last letters
+ Function<String, Flowable<Integer>> first3 =
+ word -> chars(word).take(3) ;
+ Function<String, Flowable<Integer>> last3 =
+ word -> chars(word).skip(3) ;
+
+
+ // Stream to be maxed
+ Function<String, Flowable<Integer>> toBeMaxed =
+ word -> Flowable.concat(first3.apply(word), last3.apply(word))
+ ;
+
+ // Bonus for double letter
+ Function<String, Flowable<Integer>> bonusForDoubleLetter =
+ word -> MathFlowable.max(toBeMaxed.apply(word)
+ .map(scoreOfALetter)
+ ) ;
+
+ // score of the word put on the board
+ Function<String, Flowable<Integer>> score3 =
+ word ->
+ MathFlowable.sumInt(Flowable.concat(
+ score2.apply(word),
+ bonusForDoubleLetter.apply(word)
+ )).map(v -> v * 2 + (word.length() == 7 ? 50 : 0));
+
+ Function<Function<String, Flowable<Integer>>, Single<TreeMap<Integer, List<String>>>> buildHistoOnScore =
+ score -> Flowable.fromIterable(shakespeareWords)
+ .filter(scrabbleWords::contains)
+ .filter(word -> checkBlanks.apply(word).blockingFirst())
+ .collect(
+ () -> new TreeMap<Integer, List<String>>(Comparator.reverseOrder()),
+ (TreeMap<Integer, List<String>> map, String word) -> {
+ Integer key = score.apply(word).blockingFirst() ;
+ List<String> list = map.get(key) ;
+ if (list == null) {
+ list = new ArrayList<>() ;
+ map.put(key, list) ;
+ }
+ list.add(word) ;
+ }
+ ) ;
+
+ // best key / value pairs
+ List<Entry<Integer, List<String>>> finalList2 =
+ buildHistoOnScore.apply(score3).flattenAsFlowable(
+ map -> map.entrySet()
+ )
+ .take(3)
+ .collect(
+ () -> new ArrayList<Entry<Integer, List<String>>>(),
+ (list, entry) -> {
+ list.add(entry) ;
+ }
+ )
+ .blockingGet();
+
+ return finalList2 ;
+ }
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java
new file mode 100644
index 00000000..a45dbdd2
--- /dev/null
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble.optimizations;
+
+import io.reactivex.Flowable;
+import io.reactivex.internal.fuseable.QueueFuseable;
+import io.reactivex.internal.subscriptions.BasicQueueSubscription;
+import io.reactivex.internal.subscriptions.SubscriptionHelper;
+import io.reactivex.internal.util.BackpressureHelper;
+import org.reactivestreams.Subscriber;
+
+final class FlowableCharSequence extends Flowable<Integer> {
+
+ final CharSequence string;
+
+ FlowableCharSequence(CharSequence string) {
+ this.string = string;
+ }
+
+ @Override
+ public void subscribeActual(Subscriber<? super Integer> s) {
+ s.onSubscribe(new CharSequenceSubscription(s, string));
+ }
+
+ static final class CharSequenceSubscription
+ extends BasicQueueSubscription<Integer> {
+
+ private static final long serialVersionUID = -4593793201463047197L;
+
+ final Subscriber<? super Integer> downstream;
+
+ final CharSequence string;
+
+ final int end;
+
+ int index;
+
+ volatile boolean cancelled;
+
+ CharSequenceSubscription(Subscriber<? super Integer> downstream, CharSequence string) {
+ this.downstream = downstream;
+ this.string = string;
+ this.end = string.length();
+ }
+
+ @Override
+ public void cancel() {
+ cancelled = true;
+ }
+
+ @Override
+ public void request(long n) {
+ if (SubscriptionHelper.validate(n)) {
+ if (BackpressureHelper.add(this, n) == 0) {
+ if (n == Long.MAX_VALUE) {
+ fastPath();
+ } else {
+ slowPath(n);
+ }
+ }
+ }
+ }
+
+ void fastPath() {
+ int e = end;
+ CharSequence s = string;
+ Subscriber<? super Integer> a = downstream;
+
+ for (int i = index; i != e; i++) {
+ if (cancelled) {
+ return;
+ }
+
+ a.onNext((int)s.charAt(i));
+ }
+
+ if (!cancelled) {
+ a.onComplete();
+ }
+ }
+
+ void slowPath(long r) {
+ long e = 0L;
+ int i = index;
+ int f = end;
+ CharSequence s = string;
+ Subscriber<? super Integer> a = downstream;
+
+ for (;;) {
+
+ while (e != r && i != f) {
+ if (cancelled) {
+ return;
+ }
+
+ a.onNext((int)s.charAt(i));
+
+ i++;
+ e++;
+ }
+
+ if (i == f) {
+ if (!cancelled) {
+ a.onComplete();
+ }
+ return;
+ }
+
+ r = get();
+ if (e == r) {
+ index = i;
+ r = addAndGet(-e);
+ if (r == 0L) {
+ break;
+ }
+ e = 0L;
+ }
+ }
+ }
+
+ @Override
+ public int requestFusion(int requestedMode) {
+ return requestedMode & QueueFuseable.SYNC;
+ }
+
+ @Override
+ public Integer poll() {
+ int i = index;
+ if (i != end) {
+ index = i + 1;
+ return (int)string.charAt(i);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return index == end;
+ }
+
+ @Override
+ public void clear() {
+ index = end;
+ }
+ }
+
+}
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java
new file mode 100644
index 00000000..83c203e4
--- /dev/null
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble.optimizations;
+
+import io.reactivex.Flowable;
+import io.reactivex.FlowableTransformer;
+import io.reactivex.exceptions.Exceptions;
+import io.reactivex.internal.fuseable.ConditionalSubscriber;
+import io.reactivex.internal.fuseable.SimplePlainQueue;
+import io.reactivex.internal.queue.SpscArrayQueue;
+import io.reactivex.internal.subscriptions.SubscriptionHelper;
+import io.reactivex.internal.util.BackpressureHelper;
+import io.reactivex.plugins.RxJavaPlugins;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.regex.Pattern;
+
+final class FlowableSplit extends Flowable<String> implements FlowableTransformer<String, String> {
+
+ final Publisher<String> source;
+
+ final Pattern pattern;
+
+ final int bufferSize;
+
+ FlowableSplit(Publisher<String> source, Pattern pattern, int bufferSize) {
+ this.source = source;
+ this.pattern = pattern;
+ this.bufferSize = bufferSize;
+ }
+
+ @Override
+ public Publisher<String> apply(Flowable<String> upstream) {
+ return new FlowableSplit(upstream, pattern, bufferSize);
+ }
+
+ @Override
+ protected void subscribeActual(Subscriber<? super String> s) {
+ source.subscribe(new SplitSubscriber(s, pattern, bufferSize));
+ }
+
+ static final class SplitSubscriber
+ extends AtomicInteger
+ implements ConditionalSubscriber<String>, Subscription {
+
+ static final String[] EMPTY = new String[0];
+
+ private static final long serialVersionUID = -5022617259701794064L;
+
+ final Subscriber<? super String> downstream;
+
+ final Pattern pattern;
+
+ final SimplePlainQueue<String[]> queue;
+
+ final AtomicLong requested;
+
+ final int bufferSize;
+
+ final int limit;
+
+ Subscription upstream;
+
+ volatile boolean cancelled;
+
+ String leftOver;
+
+ String[] current;
+
+ int index;
+
+ int produced;
+
+ volatile boolean done;
+ Throwable error;
+
+ int empty;
+
+ SplitSubscriber(Subscriber<? super String> downstream, Pattern pattern, int bufferSize) {
+ this.downstream = downstream;
+ this.pattern = pattern;
+ this.bufferSize = bufferSize;
+ this.limit = bufferSize - (bufferSize >> 2);
+ this.queue = new SpscArrayQueue<String[]>(bufferSize);
+ this.requested = new AtomicLong();
+ }
+
+ @Override
+ public void request(long n) {
+ if (SubscriptionHelper.validate(n)) {
+ BackpressureHelper.add(requested, n);
+ drain();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ cancelled = true;
+ upstream.cancel();
+
+ if (getAndIncrement() == 0) {
+ current = null;
+ queue.clear();
+ }
+ }
+
+ @Override
+ public void onSubscribe(Subscription s) {
+ if (SubscriptionHelper.validate(this.upstream, s)) {
+ this.upstream = s;
+
+ downstream.onSubscribe(this);
+
+ s.request(bufferSize);
+ }
+ }
+
+ @Override
+ public void onNext(String t) {
+ if (!tryOnNext(t)) {
+ upstream.request(1);
+ }
+ }
+
+ @Override
+ public boolean tryOnNext(String t) {
+ String lo = leftOver;
+ String[] a;
+ try {
+ if (lo == null || lo.isEmpty()) {
+ a = pattern.split(t, -1);
+ } else {
+ a = pattern.split(lo + t, -1);
+ }
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ this.upstream.cancel();
+ onError(ex);
+ return true;
+ }
+
+ if (a.length == 0) {
+ leftOver = null;
+ return false;
+ } else
+ if (a.length == 1) {
+ leftOver = a[0];
+ return false;
+ }
+ leftOver = a[a.length - 1];
+ queue.offer(a);
+ drain();
+ return true;
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ if (done) {
+ RxJavaPlugins.onError(t);
+ return;
+ }
+ String lo = leftOver;
+ if (lo != null && !lo.isEmpty()) {
+ leftOver = null;
+ queue.offer(new String[] { lo, null });
+ }
+ error = t;
+ done = true;
+ drain();
+ }
+
+ @Override
+ public void onComplete() {
+ if (!done) {
+ done = true;
+ String lo = leftOver;
+ if (lo != null && !lo.isEmpty()) {
+ leftOver = null;
+ queue.offer(new String[] { lo, null });
+ }
+ drain();
+ }
+ }
+
+ void drain() {
+ if (getAndIncrement() != 0) {
+ return;
+ }
+
+ SimplePlainQueue<String[]> q = queue;
+
+ int missed = 1;
+ int consumed = produced;
+ String[] array = current;
+ int idx = index;
+ int emptyCount = empty;
+
+ Subscriber<? super String> a = downstream;
+
+ for (;;) {
+ long r = requested.get();
+ long e = 0;
+
+ while (e != r) {
+ if (cancelled) {
+ current = null;
+ q.clear();
+ return;
+ }
+
+ boolean d = done;
+
+ if (array == null) {
+ array = q.poll();
+ if (array != null) {
+ current = array;
+ if (++consumed == limit) {
+ consumed = 0;
+ upstream.request(limit);
+ }
+ }
+ }
+
+ boolean empty = array == null;
+
+ if (d && empty) {
+ current = null;
+ Throwable ex = error;
+ if (ex != null) {
+ a.onError(ex);
+ } else {
+ a.onComplete();
+ }
+ return;
+ }
+
+ if (empty) {
+ break;
+ }
+
+ if (array.length == idx + 1) {
+ array = null;
+ current = null;
+ idx = 0;
+ continue;
+ }
+
+ String v = array[idx];
+
+ if (v.isEmpty()) {
+ emptyCount++;
+ idx++;
+ } else {
+ while (emptyCount != 0 && e != r) {
+ if (cancelled) {
+ current = null;
+ q.clear();
+ return;
+ }
+ a.onNext("");
+ e++;
+ emptyCount--;
+ }
+
+ if (e != r && emptyCount == 0) {
+ a.onNext(v);
+
+ e++;
+ idx++;
+ }
+ }
+ }
+
+ if (e == r) {
+ if (cancelled) {
+ current = null;
+ q.clear();
+ return;
+ }
+
+ boolean d = done;
+
+ if (array == null) {
+ array = q.poll();
+ if (array != null) {
+ current = array;
+ if (++consumed == limit) {
+ consumed = 0;
+ upstream.request(limit);
+ }
+ }
+ }
+
+ boolean empty = array == null;
+
+ if (d && empty) {
+ current = null;
+ Throwable ex = error;
+ if (ex != null) {
+ a.onError(ex);
+ } else {
+ a.onComplete();
+ }
+ return;
+ }
+ }
+
+ if (e != 0L) {
+ BackpressureHelper.produced(requested, e);
+ }
+
+ empty = emptyCount;
+ produced = consumed;
+ missed = addAndGet(-missed);
+ if (missed == 0) {
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java
new file mode 100644
index 00000000..3d36a0d8
--- /dev/null
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble.optimizations;
+
+import io.reactivex.Flowable;
+import io.reactivex.FlowableTransformer;
+import io.reactivex.internal.functions.ObjectHelper;
+import io.reactivex.plugins.RxJavaPlugins;
+
+import java.util.regex.Pattern;
+
+public final class StringFlowable {
+ /** Utility class. */
+ private StringFlowable() {
+ throw new IllegalStateException("No instances!");
+ }
+
+ /**
+ * Signals each character of the given string CharSequence as Integers.
+ * @param string the source of characters
+ * @return the new Flowable instance
+ */
+ public static Flowable<Integer> characters(CharSequence string) {
+ ObjectHelper.requireNonNull(string, "string is null");
+ return RxJavaPlugins.onAssembly(new FlowableCharSequence(string));
+ }
+
+ /**
+ * Splits the input sequence of strings based on a pattern even across subsequent
+ * elements if needed.
+ * @param pattern the Rexexp pattern to split along
+ * @return the new FlowableTransformer instance
+ *
+ * @since 0.13.0
+ */
+ public static FlowableTransformer<String, String> split(Pattern pattern) {
+ return split(pattern, Flowable.bufferSize());
+ }
+
+ /**
+ * Splits the input sequence of strings based on a pattern even across subsequent
+ * elements if needed.
+ * @param pattern the Rexexp pattern to split along
+ * @param bufferSize the number of items to prefetch from the upstream
+ * @return the new FlowableTransformer instance
+ *
+ * @since 0.13.0
+ */
+ public static FlowableTransformer<String, String> split(Pattern pattern, int bufferSize) {
+ ObjectHelper.requireNonNull(pattern, "pattern is null");
+ ObjectHelper.verifyPositive(bufferSize, "bufferSize");
+ return new FlowableSplit(null, pattern, bufferSize);
+ }
+
+ /**
+ * Splits the input sequence of strings based on a pattern even across subsequent
+ * elements if needed.
+ * @param pattern the Rexexp pattern to split along
+ * @return the new FlowableTransformer instance
+ *
+ * @since 0.13.0
+ */
+ public static FlowableTransformer<String, String> split(String pattern) {
+ return split(pattern, Flowable.bufferSize());
+ }
+
+ /**
+ * Splits the input sequence of strings based on a pattern even across subsequent
+ * elements if needed.
+ * @param pattern the Rexexp pattern to split along
+ * @param bufferSize the number of items to prefetch from the upstream
+ * @return the new FlowableTransformer instance
+ *
+ * @since 0.13.0
+ */
+ public static FlowableTransformer<String, String> split(String pattern, int bufferSize) {
+ return split(Pattern.compile(pattern), bufferSize);
+ }
+
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/CancellableContinuationBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/CancellableContinuationBenchmark.kt
new file mode 100644
index 00000000..99c0f049
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/CancellableContinuationBenchmark.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks
+
+import kotlinx.coroutines.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+@Warmup(iterations = 5)
+@Measurement(iterations = 10)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+@Fork(2)
+open class CancellableContinuationBenchmark {
+
+ @Benchmark
+ fun awaitWithSuspension(): Int {
+ val deferred = CompletableDeferred<Int>()
+ return run(allowSuspend = true) { deferred.await() }
+ }
+
+ @Benchmark
+ fun awaitNoSuspension(): Int {
+ val deferred = CompletableDeferred(1)
+ return run { deferred.await() }
+ }
+
+ private fun run(allowSuspend: Boolean = false, block: suspend () -> Int): Int {
+ val value = block.startCoroutineUninterceptedOrReturn(EmptyContinuation)
+ if (value === COROUTINE_SUSPENDED) {
+ if (!allowSuspend) {
+ throw IllegalStateException("Unexpected suspend")
+ } else {
+ return -1
+ }
+ }
+
+ return value as Int
+ }
+
+ object EmptyContinuation : Continuation<Int> {
+ override val context: CoroutineContext
+ get() = EmptyCoroutineContext
+
+ override fun resumeWith(result: Result<Int>) {
+ }
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt
new file mode 100644
index 00000000..77b907f6
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt
@@ -0,0 +1,146 @@
+package benchmarks
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.selects.select
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+import java.lang.Integer.max
+import java.util.concurrent.ForkJoinPool
+import java.util.concurrent.Phaser
+import java.util.concurrent.ThreadLocalRandom
+import java.util.concurrent.TimeUnit
+
+
+/**
+ * Benchmark to measure channel algorithm performance in terms of average time per `send-receive` pair;
+ * actually, it measures the time for a batch of such operations separated into the specified number of consumers/producers.
+ * It uses different channels (rendezvous, buffered, unlimited; see [ChannelCreator]) and different dispatchers
+ * (see [DispatcherCreator]). If the [_3_withSelect] property is set, it invokes `send` and
+ * `receive` via [select], waiting on a local dummy channel simultaneously, simulating a "cancellation" channel.
+ *
+ * Please, be patient, this benchmark takes quite a lot of time to complete.
+ */
+@Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MICROSECONDS)
+@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MICROSECONDS)
+@Fork(value = 3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class ChannelProducerConsumerBenchmark {
+ @Param
+ private var _0_dispatcher: DispatcherCreator = DispatcherCreator.FORK_JOIN
+
+ @Param
+ private var _1_channel: ChannelCreator = ChannelCreator.RENDEZVOUS
+
+ @Param("0", "1000")
+ private var _2_coroutines: Int = 0
+
+ @Param("false", "true")
+ private var _3_withSelect: Boolean = false
+
+ @Param("1", "2", "4") // local machine
+// @Param("1", "2", "4", "8", "12") // local machine
+// @Param("1", "2", "4", "8", "16", "32", "64", "128", "144") // dasquad
+// @Param("1", "2", "4", "8", "16", "32", "64", "96") // Google Cloud
+ private var _4_parallelism: Int = 0
+
+ private lateinit var dispatcher: CoroutineDispatcher
+ private lateinit var channel: Channel<Int>
+
+ @InternalCoroutinesApi
+ @Setup
+ fun setup() {
+ dispatcher = _0_dispatcher.create(_4_parallelism)
+ channel = _1_channel.create()
+ }
+
+ @Benchmark
+ fun spmc() {
+ if (_2_coroutines != 0) return
+ val producers = max(1, _4_parallelism - 1)
+ val consumers = 1
+ run(producers, consumers)
+ }
+
+ @Benchmark
+ fun mpmc() {
+ val producers = if (_2_coroutines == 0) (_4_parallelism + 1) / 2 else _2_coroutines / 2
+ val consumers = producers
+ run(producers, consumers)
+ }
+
+ private fun run(producers: Int, consumers: Int) {
+ val n = APPROX_BATCH_SIZE / producers * producers
+ val phaser = Phaser(producers + consumers + 1)
+ // Run producers
+ repeat(producers) {
+ GlobalScope.launch(dispatcher) {
+ val dummy = if (_3_withSelect) _1_channel.create() else null
+ repeat(n / producers) {
+ produce(it, dummy)
+ }
+ phaser.arrive()
+ }
+ }
+ // Run consumers
+ repeat(consumers) {
+ GlobalScope.launch(dispatcher) {
+ val dummy = if (_3_withSelect) _1_channel.create() else null
+ repeat(n / consumers) {
+ consume(dummy)
+ }
+ phaser.arrive()
+ }
+ }
+ // Wait until work is done
+ phaser.arriveAndAwaitAdvance()
+ }
+
+ private suspend fun produce(element: Int, dummy: Channel<Int>?) {
+ if (_3_withSelect) {
+ select<Unit> {
+ channel.onSend(element) {}
+ dummy!!.onReceive {}
+ }
+ } else {
+ channel.send(element)
+ }
+ doWork()
+ }
+
+ private suspend fun consume(dummy: Channel<Int>?) {
+ if (_3_withSelect) {
+ select<Unit> {
+ channel.onReceive {}
+ dummy!!.onReceive {}
+ }
+ } else {
+ channel.receive()
+ }
+ doWork()
+ }
+}
+
+enum class DispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) {
+ FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() })
+}
+
+enum class ChannelCreator(private val capacity: Int) {
+ RENDEZVOUS(Channel.RENDEZVOUS),
+// BUFFERED_1(1),
+ BUFFERED_2(2),
+// BUFFERED_4(4),
+ BUFFERED_32(32),
+ BUFFERED_128(128),
+ BUFFERED_UNLIMITED(Channel.UNLIMITED);
+
+ fun create(): Channel<Int> = Channel(capacity)
+}
+
+private fun doWork(): Unit = Blackhole.consumeCPU(ThreadLocalRandom.current().nextLong(WORK_MIN, WORK_MAX))
+
+private const val WORK_MIN = 50L
+private const val WORK_MAX = 100L
+private const val APPROX_BATCH_SIZE = 100000 \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt
new file mode 100644
index 00000000..8b5e90aa
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2018 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 = 5, time = 1)
+@Measurement(iterations = 5, time = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Fork(1)
+open class ChannelSinkBenchmark {
+ private val tl = ThreadLocal.withInitial({ 42 })
+ private val tl2 = ThreadLocal.withInitial({ 239 })
+
+ private val unconfined = Dispatchers.Unconfined
+ private val unconfinedOneElement = Dispatchers.Unconfined + tl.asContextElement()
+ private val unconfinedTwoElements = Dispatchers.Unconfined + tl.asContextElement() + tl2.asContextElement()
+
+ @Benchmark
+ fun channelPipeline(): Int = runBlocking {
+ run(unconfined)
+ }
+
+ @Benchmark
+ fun channelPipelineOneThreadLocal(): Int = runBlocking {
+ run(unconfinedOneElement)
+ }
+
+ @Benchmark
+ fun channelPipelineTwoThreadLocals(): Int = runBlocking {
+ run(unconfinedTwoElements)
+ }
+
+ private suspend inline fun run(context: CoroutineContext): Int {
+ return Channel
+ .range(1, 1_000_000, context)
+ .filter(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)
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ForkJoinBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ForkJoinBenchmark.kt
new file mode 100644
index 00000000..21d0f54b
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ForkJoinBenchmark.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks
+
+import kotlinx.coroutines.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+/*
+ * Comparison of fork-join tasks using specific FJP API and classic [async] jobs.
+ * FJP job is organized in perfectly balanced binary tree, every leaf node computes
+ * FPU-heavy sum over its data and intermediate nodes sum results.
+ *
+ * Fine-grained batch size (8192 * 1024 tasks, 128 in sequential batch)
+ * ForkJoinBenchmark.asyncExperimental avgt 10 681.512 ± 32.069 ms/op
+ * ForkJoinBenchmark.asyncFjp avgt 10 845.386 ± 73.204 ms/op
+ * ForkJoinBenchmark.fjpRecursiveTask avgt 10 692.120 ± 26.224 ms/op
+ * ForkJoinBenchmark.fjpTask avgt 10 791.087 ± 66.544 ms/op
+ *
+ * Too small tasks (8192 * 1024 tasks, 128 batch, 16 in sequential batch)
+ * Benchmark Mode Cnt Score Error Units
+ * ForkJoinBenchmark.asyncExperimental avgt 10 1273.271 ± 190.372 ms/op
+ * ForkJoinBenchmark.asyncFjp avgt 10 1406.102 ± 216.793 ms/op
+ * ForkJoinBenchmark.fjpRecursiveTask avgt 10 849.941 ± 141.254 ms/op
+ * ForkJoinBenchmark.fjpTask avgt 10 831.554 ± 57.276 ms/op
+ */
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class ForkJoinBenchmark : ParametrizedDispatcherBase() {
+
+ companion object {
+ /*
+ * Change task size to control global granularity of benchmark
+ * Change batch size to control affinity/work stealing/scheduling overhead effects
+ */
+ const val TASK_SIZE = 8192 * 1024
+ const val BATCH_SIZE = 32 * 8192
+ }
+
+ lateinit var coefficients: LongArray
+ override var dispatcher: String = "experimental"
+
+ @Setup
+ override fun setup() {
+ super.setup()
+ coefficients = LongArray(TASK_SIZE) { ThreadLocalRandom.current().nextLong(0, 1024 * 1024) }
+ }
+
+ @Benchmark
+ fun asyncFjp() = runBlocking {
+ CoroutineScope(ForkJoinPool.commonPool().asCoroutineDispatcher()).startAsync(coefficients, 0, coefficients.size).await()
+ }
+
+ @Benchmark
+ fun asyncExperimental() = runBlocking {
+ startAsync(coefficients, 0, coefficients.size).await()
+ }
+
+ @Benchmark
+ fun fjpRecursiveTask(): Double {
+ val task = RecursiveAction(coefficients, 0, coefficients.size)
+ return ForkJoinPool.commonPool().submit(task).join()
+ }
+
+ @Benchmark
+ fun fjpTask(): Double {
+ val task = Task(coefficients, 0, coefficients.size)
+ return ForkJoinPool.commonPool().submit(task).join()
+ }
+
+ suspend fun CoroutineScope.startAsync(coefficients: LongArray, start: Int, end: Int): Deferred<Double> = async {
+ if (end - start <= BATCH_SIZE) {
+ compute(coefficients, start, end)
+ } else {
+ val first = startAsync(coefficients, start, start + (end - start) / 2)
+ val second = startAsync(coefficients, start + (end - start) / 2, end)
+ first.await() + second.await()
+ }
+ }
+
+ class Task(val coefficients: LongArray, val start: Int, val end: Int) : RecursiveTask<Double>() {
+ override fun compute(): Double {
+ if (end - start <= BATCH_SIZE) {
+ return compute(coefficients, start, end)
+ }
+
+ val first = Task(coefficients, start, start + (end - start) / 2).fork()
+ val second = Task(coefficients, start + (end - start) / 2, end).fork()
+
+ var result = 0.0
+ result += first.join()
+ result += second.join()
+ return result
+ }
+
+ private fun compute(coefficients: LongArray, start: Int, end: Int): Double {
+ var result = 0.0
+ for (i in start until end) {
+ result += Math.sin(Math.pow(coefficients[i].toDouble(), 1.1)) + 1e-8
+ }
+
+ return result
+ }
+ }
+
+ class RecursiveAction(val coefficients: LongArray, val start: Int, val end: Int, @Volatile var result: Double = 0.0,
+ parent: RecursiveAction? = null) : CountedCompleter<Double>(parent) {
+
+ private var first: ForkJoinTask<Double>? = null
+ private var second: ForkJoinTask<Double>? = null
+
+ override fun getRawResult(): Double {
+ return result
+ }
+
+ override fun setRawResult(t: Double) {
+ result = t
+ }
+
+ override fun compute() {
+ if (end - start <= BATCH_SIZE) {
+ rawResult = compute(coefficients, start, end)
+ } else {
+ pendingCount = 2
+ // One may fork only once here and executing second task here with looping over firstComplete to be even more efficient
+ first = RecursiveAction(coefficients, start, start + (end - start) / 2, parent = this).fork()
+ second = RecursiveAction(coefficients, start + (end - start) / 2, end, parent = this).fork()
+ }
+
+ tryComplete()
+ }
+
+ override fun onCompletion(caller: CountedCompleter<*>?) {
+ if (caller !== this) {
+ rawResult = first!!.rawResult + second!!.rawResult
+ }
+ super.onCompletion(caller)
+ }
+ }
+}
+
+
+private fun compute(coefficients: LongArray, start: Int, end: Int): Double {
+ var result = 0.0
+ for (i in start until end) {
+ result += Math.sin(Math.pow(coefficients[i].toDouble(), 1.1)) + 1e-8
+ }
+
+ return result
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/LaunchBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/LaunchBenchmark.kt
new file mode 100644
index 00000000..2639dbb2
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/LaunchBenchmark.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks
+
+import kotlinx.coroutines.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+/*
+ * Benchmark to measure scheduling overhead in comparison with FJP.
+ * LaunchBenchmark.massiveLaunch experimental avgt 30 328.662 ± 52.789 us/op
+ * LaunchBenchmark.massiveLaunch fjp avgt 30 179.762 ± 3.931 us/op
+ */
+@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+open class LaunchBenchmark : ParametrizedDispatcherBase() {
+
+ @Param("experimental", "fjp")
+ override var dispatcher: String = "fjp"
+
+ private val jobsToLaunch = 100
+ private val submitters = 4
+
+ private val allLaunched = CyclicBarrier(submitters)
+ private val stopBarrier = CyclicBarrier(submitters + 1)
+
+ @Benchmark
+ fun massiveLaunch() {
+ repeat(submitters) {
+ launch {
+ // Wait until all cores are occupied
+ allLaunched.await()
+ allLaunched.reset()
+
+ (1..jobsToLaunch).map {
+ launch {
+ // do nothing
+ }
+ }.map { it.join() }
+
+ stopBarrier.await()
+ }
+ }
+
+ stopBarrier.await()
+ stopBarrier.reset()
+ }
+
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
new file mode 100644
index 00000000..f8e88bf6
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks
+
+import benchmarks.actors.CORES_COUNT
+import kotlinx.coroutines.*
+import kotlinx.coroutines.scheduling.*
+import org.openjdk.jmh.annotations.Param
+import org.openjdk.jmh.annotations.Setup
+import org.openjdk.jmh.annotations.TearDown
+import java.io.Closeable
+import java.util.concurrent.*
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Base class to use different [CoroutineContext] in benchmarks via [Param] in inheritors.
+ * Currently allowed values are "fjp" for [CommonPool] and ftp_n for [ThreadPoolDispatcher] with n threads.
+ */
+abstract class ParametrizedDispatcherBase : CoroutineScope {
+
+ abstract var dispatcher: String
+ override lateinit var coroutineContext: CoroutineContext
+ var closeable: Closeable? = null
+
+ @UseExperimental(InternalCoroutinesApi::class)
+ @Setup
+ open fun setup() {
+ coroutineContext = when {
+ dispatcher == "fjp" -> ForkJoinPool.commonPool().asCoroutineDispatcher()
+ dispatcher == "experimental" -> {
+ ExperimentalCoroutineDispatcher(CORES_COUNT).also { closeable = it }
+ }
+ dispatcher.startsWith("ftp") -> {
+ newFixedThreadPoolContext(dispatcher.substring(4).toInt(), dispatcher).also { closeable = it }
+ }
+ else -> error("Unexpected dispatcher: $dispatcher")
+ }
+ }
+
+ @TearDown
+ fun tearDown() {
+ closeable?.close()
+ }
+
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
new file mode 100644
index 00000000..0fc563a8
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
@@ -0,0 +1,97 @@
+package benchmarks
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher
+import kotlinx.coroutines.sync.Semaphore
+import kotlinx.coroutines.sync.withPermit
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.ForkJoinPool
+import java.util.concurrent.ThreadLocalRandom
+import java.util.concurrent.TimeUnit
+
+@Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MICROSECONDS)
+@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MICROSECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class SemaphoreBenchmark {
+ @Param
+ private var _1_dispatcher: SemaphoreBenchDispatcherCreator = SemaphoreBenchDispatcherCreator.FORK_JOIN
+
+ @Param("0", "1000")
+ private var _2_coroutines: Int = 0
+
+ @Param("1", "2", "4", "8", "32", "128", "100000")
+ private var _3_maxPermits: Int = 0
+
+ @Param("1", "2", "4") // local machine
+// @Param("1", "2", "4", "8", "16", "32", "64", "128", "144") // dasquad
+// @Param("1", "2", "4", "8", "16", "32", "64", "96") // Google Cloud
+ private var _4_parallelism: Int = 0
+
+ private lateinit var dispatcher: CoroutineDispatcher
+ private var coroutines = 0
+
+ @InternalCoroutinesApi
+ @Setup
+ fun setup() {
+ dispatcher = _1_dispatcher.create(_4_parallelism)
+ coroutines = if (_2_coroutines == 0) _4_parallelism else _2_coroutines
+ }
+
+ @Benchmark
+ fun semaphore() = runBlocking {
+ val n = BATCH_SIZE / coroutines
+ val semaphore = Semaphore(_3_maxPermits)
+ val jobs = ArrayList<Job>(coroutines)
+ repeat(coroutines) {
+ jobs += GlobalScope.launch {
+ repeat(n) {
+ semaphore.withPermit {
+ doWork(WORK_INSIDE)
+ }
+ doWork(WORK_OUTSIDE)
+ }
+ }
+ }
+ jobs.forEach { it.join() }
+ }
+
+ @Benchmark
+ fun channelAsSemaphore() = runBlocking {
+ val n = BATCH_SIZE / coroutines
+ val semaphore = Channel<Unit>(_3_maxPermits)
+ val jobs = ArrayList<Job>(coroutines)
+ repeat(coroutines) {
+ jobs += GlobalScope.launch {
+ repeat(n) {
+ semaphore.send(Unit) // acquire
+ doWork(WORK_INSIDE)
+ semaphore.receive() // release
+ doWork(WORK_OUTSIDE)
+ }
+ }
+ }
+ jobs.forEach { it.join() }
+ }
+}
+
+enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) {
+ FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }),
+ EXPERIMENTAL({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) })
+}
+
+private fun doWork(work: Int) {
+ // We use geometric distribution here
+ val p = 1.0 / work
+ val r = ThreadLocalRandom.current()
+ while (true) {
+ if (r.nextDouble() < p) break
+ }
+}
+
+private const val WORK_INSIDE = 80
+private const val WORK_OUTSIDE = 40
+private const val BATCH_SIZE = 1000000 \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/StatefulAwaitsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/StatefulAwaitsBenchmark.kt
new file mode 100644
index 00000000..8fdb146f
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/StatefulAwaitsBenchmark.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016-2018 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.*
+
+/*
+ * Benchmark which launches multiple async jobs each with either own private or global shared state,
+ * each job iterates over its state multiple times and suspends after every iteration.
+ * Benchmark is intended to indicate pros and cons of coroutines affinity (assuming threads are rarely migrated)
+ * and comparison with single thread and ForkJoinPool
+ *
+ * Benchmark (dispatcher) (jobsCount) Mode Cnt Score Error Units
+ * StatefulAsyncBenchmark.dependentStateAsync fjp 1 avgt 10 42.147 ± 11.563 us/op
+ * StatefulAsyncBenchmark.dependentStateAsync fjp 8 avgt 10 111.053 ± 40.097 us/op
+ * StatefulAsyncBenchmark.dependentStateAsync fjp 16 avgt 10 239.992 ± 52.839 us/op
+ * StatefulAsyncBenchmark.dependentStateAsync ftp_1 1 avgt 10 32.851 ± 11.385 us/op
+ * StatefulAsyncBenchmark.dependentStateAsync ftp_1 8 avgt 10 51.692 ± 0.961 us/op
+ * StatefulAsyncBenchmark.dependentStateAsync ftp_1 16 avgt 10 101.511 ± 3.060 us/op
+ * StatefulAsyncBenchmark.dependentStateAsync ftp_8 1 avgt 10 31.549 ± 1.014 us/op
+ * StatefulAsyncBenchmark.dependentStateAsync ftp_8 8 avgt 10 103.990 ± 1.588 us/op
+ * StatefulAsyncBenchmark.dependentStateAsync ftp_8 16 avgt 10 156.384 ± 2.914 us/op
+ *
+ * StatefulAsyncBenchmark.independentStateAsync fjp 1 avgt 10 32.503 ± 0.721 us/op
+ * StatefulAsyncBenchmark.independentStateAsync fjp 8 avgt 10 73.000 ± 1.686 us/op
+ * StatefulAsyncBenchmark.independentStateAsync fjp 16 avgt 10 98.629 ± 7.541 us/op
+ * StatefulAsyncBenchmark.independentStateAsync ftp_1 1 avgt 10 26.111 ± 0.814 us/op
+ * StatefulAsyncBenchmark.independentStateAsync ftp_1 8 avgt 10 54.644 ± 1.261 us/op
+ * StatefulAsyncBenchmark.independentStateAsync ftp_1 16 avgt 10 104.871 ± 1.599 us/op
+ * StatefulAsyncBenchmark.independentStateAsync ftp_8 1 avgt 10 31.929 ± 0.698 us/op
+ * StatefulAsyncBenchmark.independentStateAsync ftp_8 8 avgt 10 108.959 ± 1.029 us/op
+ * StatefulAsyncBenchmark.independentStateAsync ftp_8 16 avgt 10 159.593 ± 5.262 us/op
+ *
+ */
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+open class StatefulAsyncBenchmark : ParametrizedDispatcherBase() {
+
+ private val stateSize = 2048
+ private val jobSuspensions = 2 // multiplicative factor for throughput
+
+ // it's useful to have more jobs than cores so run queue always will be non empty
+ @Param("1", "8", "16")
+ var jobsCount = 1
+
+ @Param("fjp", "ftp_1", "ftp_8")
+ override var dispatcher: String = "fjp"
+
+ @Volatile
+ private var state: Array<LongArray>? = null
+
+ @Setup
+ override fun setup() {
+ super.setup()
+ state = Array(Runtime.getRuntime().availableProcessors() * 4) { LongArray(stateSize) { ThreadLocalRandom.current().nextLong() } }
+ }
+
+ @Benchmark
+ fun independentStateAsync() = runBlocking {
+ val broadcastChannel = BroadcastChannel<Int>(1)
+ val subscriptionChannel = Channel<Int>(jobsCount)
+ val jobs= (0 until jobsCount).map { launchJob(it, broadcastChannel, subscriptionChannel) }.toList()
+
+ repeat(jobsCount) {
+ subscriptionChannel.receive() // await all jobs to start
+ }
+
+ // Fire barrier to start execution
+ broadcastChannel.send(1)
+ jobs.forEach { it.await() }
+ }
+
+ @Benchmark
+ fun dependentStateAsync() = runBlocking {
+ val broadcastChannel = BroadcastChannel<Int>(1)
+ val subscriptionChannel = Channel<Int>(jobsCount)
+ val jobs= (0 until jobsCount).map { launchJob(0, broadcastChannel, subscriptionChannel) }.toList()
+
+ repeat(jobsCount) {
+ subscriptionChannel.receive() // await all jobs to start
+ }
+
+ // Fire barrier to start execution
+ broadcastChannel.send(1)
+ jobs.forEach { it.await() }
+ }
+
+ private fun launchJob(
+ stateNum: Int,
+ channel: BroadcastChannel<Int>,
+ subscriptionChannel: Channel<Int>
+ ): Deferred<Long> =
+ async {
+ val subscription = channel.openSubscription()
+ subscriptionChannel.send(1)
+ subscription.receive()
+
+ var sum = 0L
+ repeat(jobSuspensions) {
+ val arr = state!![stateNum]
+ for (i in 0 until stateSize) {
+ sum += arr[i]
+
+ }
+
+ yield()
+ }
+ sum
+ }
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/actors/ConcurrentStatefulActorBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/actors/ConcurrentStatefulActorBenchmark.kt
new file mode 100644
index 00000000..db3195ff
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/actors/ConcurrentStatefulActorBenchmark.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.actors
+
+import benchmarks.*
+import benchmarks.actors.StatefulActorBenchmark.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+/*
+ * Noisy benchmarks useful to measure scheduling fairness and migration of affinity-sensitive tasks.
+ *
+ * Benchmark: single actor fans out requests to all (#cores count) computation actors and then ping pongs each in loop.
+ * Fair benchmark expects that every computation actor will receive exactly N messages, unfair expects N * cores messages received in total.
+ *
+ * Benchmark (dispatcher) (stateSize) Mode Cnt Score Error Units
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair fjp 1024 avgt 5 215.439 ± 29.685 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair ftp_1 1024 avgt 5 85.374 ± 4.477 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair ftp_8 1024 avgt 5 418.510 ± 46.906 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair experimental 1024 avgt 5 165.250 ± 20.309 ms/op
+ *
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair fjp 8192 avgt 5 220.576 ± 35.596 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair ftp_1 8192 avgt 5 298.276 ± 22.256 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair ftp_8 8192 avgt 5 426.105 ± 29.870 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair experimental 8192 avgt 5 288.546 ± 20.280 ms/op
+ *
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair fjp 262144 avgt 5 4146.057 ± 284.377 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair ftp_1 262144 avgt 5 10250.107 ± 1421.253 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair ftp_8 262144 avgt 5 6761.283 ± 4091.452 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsFair experimental 262144 avgt 5 6521.436 ± 346.726 ms/op
+ *
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair fjp 1024 avgt 5 289.875 ± 14.241 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair ftp_1 1024 avgt 5 87.336 ± 5.160 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair ftp_8 1024 avgt 5 430.718 ± 23.497 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair experimental 1024 avgt 5 153.704 ± 13.869 ms/op
+ *
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair fjp 8192 avgt 5 289.836 ± 9.719 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair ftp_1 8192 avgt 5 299.523 ± 17.357 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair ftp_8 8192 avgt 5 433.959 ± 27.669 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair experimental 8192 avgt 5 283.441 ± 22.740 ms/op
+ *
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair fjp 262144 avgt 5 7804.066 ± 1386.595 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair ftp_1 262144 avgt 5 11142.530 ± 381.401 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair ftp_8 262144 avgt 5 7739.136 ± 1317.885 ms/op
+ * ConcurrentStatefulActorBenchmark.multipleComputationsUnfair experimental 262144 avgt 5 7076.911 ± 1971.615 ms/op
+ *
+ */
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class ConcurrentStatefulActorBenchmark : ParametrizedDispatcherBase() {
+
+ @Param("1024", "8192", "262144")
+ var stateSize: Int = -1
+
+ @Param("fjp", "ftp_1", "ftp_8", "experimental")
+ override var dispatcher: String = "fjp"
+
+ @Benchmark
+ fun multipleComputationsUnfair() = runBlocking {
+ val resultChannel: Channel<Unit> = Channel(1)
+ val computations = (0 until CORES_COUNT).map { computationActor(stateSize) }
+ val requestor = requestorActorUnfair(computations, resultChannel)
+ requestor.send(Letter(Start(), Channel(0)))
+ resultChannel.receive()
+ }
+
+ @Benchmark
+ fun multipleComputationsFair() = runBlocking {
+ val resultChannel: Channel<Unit> = Channel(1)
+ val computations = (0 until CORES_COUNT).map { computationActor(stateSize) }
+ val requestor = requestorActorFair(computations, resultChannel)
+ requestor.send(Letter(Start(), Channel(0)))
+ resultChannel.receive()
+ }
+
+ fun requestorActorUnfair(
+ computations: List<SendChannel<Letter>>,
+ stopChannel: Channel<Unit>
+ ) =
+ actor<Letter>(capacity = 1024) {
+ var received = 0
+ for (letter in channel) with(letter) {
+ when (message) {
+ is Start -> {
+ computations.shuffled()
+ .forEach { it.send(Letter(ThreadLocalRandom.current().nextLong(), channel)) }
+ }
+ is Long -> {
+ if (++received >= ROUNDS * 8) {
+ stopChannel.send(Unit)
+ return@actor
+ } else {
+ sender.send(Letter(ThreadLocalRandom.current().nextLong(), channel))
+ }
+ }
+ else -> error("Cannot happen: $letter")
+ }
+ }
+ }
+
+ fun requestorActorFair(
+ computations: List<SendChannel<Letter>>,
+ stopChannel: Channel<Unit>
+ ) =
+ actor<Letter>(capacity = 1024) {
+ val received = hashMapOf(*computations.map { it to 0 }.toTypedArray())
+ var receivedTotal = 0
+
+ for (letter in channel) with(letter) {
+ when (message) {
+ is Start -> {
+ computations.shuffled()
+ .forEach { it.send(Letter(ThreadLocalRandom.current().nextLong(), channel)) }
+ }
+ is Long -> {
+ if (++receivedTotal >= ROUNDS * computations.size) {
+ stopChannel.send(Unit)
+ return@actor
+ } else {
+ val receivedFromSender = received[sender]!!
+ if (receivedFromSender <= ROUNDS) {
+ received[sender] = receivedFromSender + 1
+ sender.send(Letter(ThreadLocalRandom.current().nextLong(), channel))
+ }
+ }
+ }
+ else -> error("Cannot happen: $letter")
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/actors/CycledActorsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/actors/CycledActorsBenchmark.kt
new file mode 100644
index 00000000..385693e3
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/actors/CycledActorsBenchmark.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.actors
+
+import benchmarks.*
+import benchmarks.actors.PingPongActorBenchmark.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+/*
+ * Cores count actors chained into single cycle pass message and process it using its private state.
+ *
+ * Benchmark (actorStateSize) (dispatcher) Mode Cnt Score Error Units
+ * CycledActorsBenchmark.cycledActors 1 fjp avgt 14 22.751 ± 1.351 ms/op
+ * CycledActorsBenchmark.cycledActors 1 ftp_1 avgt 14 4.535 ± 0.076 ms/op
+ * CycledActorsBenchmark.cycledActors 1 experimental avgt 14 6.728 ± 0.048 ms/op
+ *
+ * CycledActorsBenchmark.cycledActors 1024 fjp avgt 14 43.725 ± 14.393 ms/op
+ * CycledActorsBenchmark.cycledActors 1024 ftp_1 avgt 14 13.827 ± 1.554 ms/op
+ * CycledActorsBenchmark.cycledActors 1024 experimental avgt 14 23.823 ± 1.643 ms/op
+ *
+ * CycledActorsBenchmark.cycledActors 262144 fjp avgt 14 1885.708 ± 532.634 ms/op
+ * CycledActorsBenchmark.cycledActors 262144 ftp_1 avgt 14 1394.997 ± 101.938 ms/op
+ * CycledActorsBenchmark.cycledActors 262144 experimental avgt 14 1804.146 ± 57.275 ms/op
+ */
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class CycledActorsBenchmark : ParametrizedDispatcherBase() {
+
+ companion object {
+ val NO_CHANNEL = Channel<Letter>(0)
+ }
+
+ @Param("fjp", "ftp_1", "experimental")
+ override var dispatcher: String = "fjp"
+
+ @Param("524288")
+ var actorStateSize = 1
+
+ @Benchmark
+ fun cycledActors() = runBlocking {
+ val stopChannel = Channel<Unit>(CORES_COUNT)
+ runCycle(stopChannel)
+ repeat(CORES_COUNT) {
+ stopChannel.receive()
+ }
+ }
+
+ private suspend fun runCycle(stopChannel: Channel<Unit>) {
+ val trailingActor = lastActor(stopChannel)
+
+ var previous = trailingActor
+ for (i in 1 until CORES_COUNT) {
+ previous = createActor(previous, stopChannel)
+ }
+
+ trailingActor.send(Letter(Start(), previous))
+ }
+
+ private fun lastActor(stopChannel: Channel<Unit>) = actor<Letter>(capacity = 1024) {
+ var nextChannel: SendChannel<Letter>? = null
+ val state = LongArray(actorStateSize) { ThreadLocalRandom.current().nextLong(1024) }
+
+ for (letter in channel) with(letter) {
+ when (message) {
+ is Start -> {
+ nextChannel = sender
+ sender.send(Letter(Ball(ThreadLocalRandom.current().nextInt(1, 100)), NO_CHANNEL))
+ }
+ is Ball -> {
+ nextChannel!!.send(Letter(Ball(transmogrify(message.count, state)), NO_CHANNEL))
+ }
+ is Stop -> {
+ stopChannel.send(Unit)
+ return@actor
+ }
+ else -> error("Can't happen")
+ }
+ }
+ }
+
+ private fun createActor(nextActor: SendChannel<Letter>, stopChannel: Channel<Unit>) = actor<Letter>(capacity = 1024) {
+ var received = 0
+ val state = LongArray(actorStateSize) { ThreadLocalRandom.current().nextLong(1024) }
+
+ for (letter in channel) with(letter) {
+ when (message) {
+ is Ball -> {
+ if (++received > 1_000) {
+ nextActor.send(Letter(Stop(), NO_CHANNEL))
+ stopChannel.send(Unit)
+ return@actor
+ } else {
+ nextActor.send(Letter(Ball(transmogrify(message.count, state)), NO_CHANNEL))
+ }
+ }
+ is Stop -> {
+ nextActor.send(Letter(Stop(), NO_CHANNEL))
+ stopChannel.send(Unit)
+ }
+ else -> error("Can't happen")
+ }
+ }
+ }
+
+ private fun transmogrify(value: Int, coefficients: LongArray): Int {
+ var result = 0L
+ for (coefficient in coefficients) {
+ result += coefficient * value
+ }
+
+ return result.toInt()
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongActorBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongActorBenchmark.kt
new file mode 100644
index 00000000..82e9b152
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongActorBenchmark.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.actors
+
+import benchmarks.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+/*
+ * Benchmark (dispatcher) Mode Cnt Score Error Units
+ * PingPongActorBenchmark.coresCountPingPongs experimental avgt 10 185.066 ± 21.692 ms/op
+ * PingPongActorBenchmark.coresCountPingPongs fjp avgt 10 200.581 ± 22.669 ms/op
+ * PingPongActorBenchmark.coresCountPingPongs ftp_1 avgt 10 494.334 ± 27.450 ms/op
+ * PingPongActorBenchmark.coresCountPingPongs ftp_2 avgt 10 498.754 ± 27.743 ms/op
+ * PingPongActorBenchmark.coresCountPingPongs ftp_8 avgt 10 804.498 ± 69.826 ms/op
+ *
+ * PingPongActorBenchmark.singlePingPong experimental avgt 10 45.521 ± 3.281 ms/op
+ * PingPongActorBenchmark.singlePingPong fjp avgt 10 217.005 ± 18.693 ms/op
+ * PingPongActorBenchmark.singlePingPong ftp_1 avgt 10 57.632 ± 1.835 ms/op
+ * PingPongActorBenchmark.singlePingPong ftp_2 avgt 10 112.723 ± 5.280 ms/op
+ * PingPongActorBenchmark.singlePingPong ftp_8 avgt 10 276.958 ± 21.447 ms/op
+ */
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class PingPongActorBenchmark : ParametrizedDispatcherBase() {
+ data class Letter(val message: Any?, val sender: SendChannel<Letter>)
+
+ @Param("experimental", "fjp", "ftp_1", "ftp_8")
+ override var dispatcher: String = "fjp"
+
+ @Benchmark
+ fun singlePingPong() = runBlocking {
+ runPingPongs(1)
+ }
+
+ @Benchmark
+ fun coresCountPingPongs() = runBlocking {
+ runPingPongs(Runtime.getRuntime().availableProcessors())
+ }
+
+ private suspend fun runPingPongs(count: Int) {
+ val me = Channel<Letter>()
+ repeat(count) {
+ val pong = pongActorCoroutine()
+ val ping = pingActorCoroutine(pong)
+ ping.send(Letter(Start(), me))
+ }
+
+ repeat(count) {
+ me.receive()
+ }
+ }
+}
+
+fun CoroutineScope.pingActorCoroutine(
+ pingChannel: SendChannel<PingPongActorBenchmark.Letter>,
+ capacity: Int = 1
+) =
+ actor<PingPongActorBenchmark.Letter>(capacity = capacity) {
+ var initiator: SendChannel<PingPongActorBenchmark.Letter>? = null
+ for (letter in channel) with(letter) {
+ when (message) {
+ is Start -> {
+ initiator = sender
+ pingChannel.send(PingPongActorBenchmark.Letter(Ball(0), channel))
+ }
+ is Ball -> {
+ pingChannel.send(PingPongActorBenchmark.Letter(Ball(message.count + 1), channel))
+ }
+ is Stop -> {
+ initiator!!.send(PingPongActorBenchmark.Letter(Stop(), channel))
+ return@actor
+ }
+ else -> error("Cannot happen $message")
+ }
+ }
+ }
+
+fun CoroutineScope.pongActorCoroutine(capacity: Int = 1) =
+ actor<PingPongActorBenchmark.Letter>(capacity = capacity) {
+ for (letter in channel) with(letter) {
+ when (message) {
+ is Ball -> {
+ if (message.count >= N_MESSAGES) {
+ sender.send(PingPongActorBenchmark.Letter(Stop(), channel))
+ return@actor
+ } else {
+ sender.send(PingPongActorBenchmark.Letter(Ball(message.count + 1), channel))
+ }
+ }
+ else -> error("Cannot happen $message")
+ }
+ }
+ } \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongAkkaBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongAkkaBenchmark.kt
new file mode 100644
index 00000000..6b71e35f
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongAkkaBenchmark.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.actors
+
+import akka.actor.ActorRef
+import akka.actor.ActorSystem
+import akka.actor.Props
+import akka.actor.UntypedAbstractActor
+import com.typesafe.config.ConfigFactory
+import org.openjdk.jmh.annotations.*
+import scala.concurrent.Await
+import scala.concurrent.duration.Duration
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+const val N_MESSAGES = 100_000
+
+data class Ball(val count: Int)
+class Start
+class Stop
+
+/*
+ * Benchmark (dispatcher) Mode Cnt Score Error Units
+ * PingPongAkkaBenchmark.coresCountPingPongs default-dispatcher avgt 10 277.501 ± 38.583 ms/op
+ * PingPongAkkaBenchmark.coresCountPingPongs single-thread-dispatcher avgt 10 196.192 ± 9.889 ms/op
+ *
+ * PingPongAkkaBenchmark.singlePingPong default-dispatcher avgt 10 173.742 ± 41.984 ms/op
+ * PingPongAkkaBenchmark.singlePingPong single-thread-dispatcher avgt 10 24.181 ± 0.730 ms/op
+ */
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class PingPongAkkaBenchmark {
+
+ lateinit var system: ActorSystem
+
+ @Param("default-dispatcher", "single-thread-dispatcher")
+ var dispatcher: String = "akka.actor.default-dispatcher"
+
+ @Setup
+ fun setup() {
+ system = ActorSystem.create("PingPong", ConfigFactory.parseString("""
+ akka.actor.single-thread-dispatcher {
+ type = Dispatcher
+ executor = "thread-pool-executor"
+ thread-pool-executor {
+ fixed-pool-size = 1
+ }
+ throughput = 1
+ }
+ """.trimIndent()
+ ))
+ }
+
+ @TearDown
+ fun tearDown() {
+ Await.ready(system.terminate(), Duration.Inf())
+ }
+
+ @Benchmark
+ fun singlePingPong() {
+ runPingPongs(1)
+ }
+
+ @Benchmark
+ fun coresCountPingPongs() {
+ runPingPongs(Runtime.getRuntime().availableProcessors())
+ }
+
+ private fun runPingPongs(pairsCount: Int) {
+ val latch = CountDownLatch(pairsCount)
+ repeat(pairsCount) {
+ val pongRef = system.actorOf(Props.create(PongActorAkka::class.java)
+ .withDispatcher("akka.actor.$dispatcher"))
+ val pingRef = system.actorOf(Props.create(PingActorAkka::class.java, pongRef, latch)
+ .withDispatcher("akka.actor.$dispatcher"))
+ pingRef.tell(Start(), ActorRef.noSender())
+ }
+
+ latch.await()
+ }
+
+ class PingActorAkka(val pongRef: ActorRef, val stopLatch: CountDownLatch) : UntypedAbstractActor() {
+ override fun onReceive(msg: Any?) {
+ when (msg) {
+ is Start -> {
+ pongRef.tell(Ball(0), self)
+ }
+ is Ball -> {
+ pongRef.tell(Ball(count = msg.count + 1), self)
+ }
+ is Stop -> {
+ stopLatch.countDown()
+ context.stop(self)
+ }
+ else -> unhandled(msg)
+ }
+ }
+ }
+
+ class PongActorAkka : UntypedAbstractActor() {
+ override fun onReceive(msg: Any?) {
+ when (msg) {
+ is Ball -> {
+ if (msg.count >= N_MESSAGES) {
+ sender.tell(Stop(), self)
+ context.stop(self)
+ } else {
+ sender.tell(Ball(msg.count + 1), self)
+ }
+ }
+ else -> unhandled(msg)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongWithBlockingContext.kt b/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongWithBlockingContext.kt
new file mode 100644
index 00000000..c6afdced
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/actors/PingPongWithBlockingContext.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.actors
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.scheduling.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+
+/*
+ * Benchmark Mode Cnt Score Error Units
+ * PingPongWithBlockingContext.commonPoolWithContextPingPong avgt 20 972.662 ± 103.448 ms/op
+ * PingPongWithBlockingContext.limitingDispatcherPingPong avgt 20 136.167 ± 4.971 ms/op
+ * PingPongWithBlockingContext.withContextPingPong avgt 20 761.669 ± 41.371 ms/op
+ */
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class PingPongWithBlockingContext {
+
+ @UseExperimental(InternalCoroutinesApi::class)
+ private val experimental = ExperimentalCoroutineDispatcher(8)
+ @UseExperimental(InternalCoroutinesApi::class)
+ private val blocking = experimental.blocking(8)
+ private val threadPool = newFixedThreadPoolContext(8, "PongCtx")
+
+ @TearDown
+ fun tearDown() {
+ threadPool.close()
+ }
+
+
+ @Benchmark
+ fun limitingDispatcherPingPong() = runBlocking {
+ runPingPongs(experimental, blocking)
+ }
+
+
+ @Benchmark
+ fun withContextPingPong() = runBlocking {
+ runPingPongs(experimental, threadPool)
+ }
+
+ @Benchmark
+ fun commonPoolWithContextPingPong() = runBlocking {
+ runPingPongs(ForkJoinPool.commonPool().asCoroutineDispatcher(), threadPool)
+ }
+
+ private suspend fun runPingPongs(pingContext: CoroutineContext, pongContext: CoroutineContext) {
+ val me = Channel<PingPongActorBenchmark.Letter>()
+ val pong = CoroutineScope(pongContext).pongActorCoroutine()
+ val ping = CoroutineScope(pingContext).pingActorCoroutine(pong)
+ ping.send(PingPongActorBenchmark.Letter(Start(), me))
+
+ me.receive()
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/actors/StatefulActorAkkaBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/actors/StatefulActorAkkaBenchmark.kt
new file mode 100644
index 00000000..c19c91fa
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/actors/StatefulActorAkkaBenchmark.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.actors
+
+import akka.actor.ActorRef
+import akka.actor.ActorSystem
+import akka.actor.Props
+import akka.actor.UntypedAbstractActor
+import com.typesafe.config.ConfigFactory
+import org.openjdk.jmh.annotations.*
+import scala.concurrent.Await
+import scala.concurrent.duration.Duration
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ThreadLocalRandom
+import java.util.concurrent.TimeUnit
+
+const val ROUNDS = 10_000
+const val STATE_SIZE = 1024
+val CORES_COUNT = Runtime.getRuntime().availableProcessors()
+
+/*
+ * Benchmarks following computation pattern:
+ * N actors, each has independent state (coefficients), receives numbers and answers with product and
+ * N requestors, which randomly send requests. N roundtrips over every requestor are measured
+ *
+ * Benchmark (dispatcher) Mode Cnt Score Error Units
+ * StatefulActorAkkaBenchmark.multipleComputationsMultipleRequestors default-dispatcher avgt 14 72.568 ± 10.620 ms/op
+ * StatefulActorAkkaBenchmark.multipleComputationsMultipleRequestors single-thread-dispatcher avgt 14 70.198 ± 3.594 ms/op
+ *
+ * StatefulActorAkkaBenchmark.multipleComputationsSingleRequestor default-dispatcher avgt 14 36.737 ± 3.589 ms/op
+ * StatefulActorAkkaBenchmark.multipleComputationsSingleRequestor single-thread-dispatcher avgt 14 9.050 ± 0.385 ms/op
+ *
+ * StatefulActorAkkaBenchmark.singleComputationMultipleRequestors default-dispatcher avgt 14 446.563 ± 85.577 ms/op
+ * StatefulActorAkkaBenchmark.singleComputationMultipleRequestors single-thread-dispatcher avgt 14 70.250 ± 3.104 ms/op
+ *
+ * StatefulActorAkkaBenchmark.singleComputationSingleRequestor default-dispatcher avgt 14 39.964 ± 2.343 ms/op
+ * StatefulActorAkkaBenchmark.singleComputationSingleRequestor single-thread-dispatcher avgt 14 10.214 ± 2.152 ms/op
+ */
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class StatefulActorAkkaBenchmark {
+
+ lateinit var system: ActorSystem
+
+ @Param("default-dispatcher", "single-thread-dispatcher")
+ var dispatcher: String = "akka.actor.default-dispatcher"
+
+ @Setup
+ fun setup() {
+ // TODO extract it to common AkkaBase if new benchmark will appear
+ system = ActorSystem.create("StatefulActors", ConfigFactory.parseString("""
+ akka.actor.single-thread-dispatcher {
+ type = Dispatcher
+ executor = "thread-pool-executor"
+ thread-pool-executor {
+ fixed-pool-size = 1
+ }
+ throughput = 1
+ }
+ """.trimIndent()
+ ))
+ }
+
+ @TearDown
+ fun tearDown() {
+ Await.ready(system.terminate(), Duration.Inf())
+ }
+
+ @Benchmark
+ fun singleComputationSingleRequestor() {
+ run(1, 1)
+ }
+
+ @Benchmark
+ fun singleComputationMultipleRequestors() {
+ run(1, CORES_COUNT)
+ }
+
+ @Benchmark
+ fun multipleComputationsSingleRequestor() {
+ run(CORES_COUNT, 1)
+ }
+
+ @Benchmark
+ fun multipleComputationsMultipleRequestors() {
+ run(CORES_COUNT, CORES_COUNT)
+ }
+
+ private fun run(computationActors: Int, requestorActors: Int) {
+ val stopLatch = CountDownLatch(requestorActors)
+ /*
+ * For complex setups Akka creates actors slowly,
+ * so first start message may become dead letter (and freeze benchmark)
+ */
+ val initLatch = CountDownLatch(computationActors + requestorActors)
+ val computations = createComputationActors(initLatch, computationActors)
+ val requestors = createRequestorActors(requestorActors, computations, initLatch, stopLatch)
+
+ initLatch.await()
+ for (requestor in requestors) {
+ requestor.tell(1L, ActorRef.noSender())
+ }
+
+ stopLatch.await()
+ computations.forEach { it.tell(Stop(), ActorRef.noSender()) }
+ }
+
+ private fun createRequestorActors(requestorActors: Int, computations: List<ActorRef>, initLatch: CountDownLatch, stopLatch: CountDownLatch): List<ActorRef> {
+ return (0 until requestorActors).map {
+ system.actorOf(Props.create(RequestorActor::class.java, computations, initLatch, stopLatch)
+ .withDispatcher("akka.actor.$dispatcher"))
+ }
+ }
+
+ private fun createComputationActors(initLatch: CountDownLatch, count: Int): List<ActorRef> {
+ return (0 until count).map {
+ system.actorOf(Props.create(ComputationActor::class.java,
+ LongArray(STATE_SIZE) { ThreadLocalRandom.current().nextLong(0, 100) }, initLatch)
+ .withDispatcher("akka.actor.$dispatcher"))
+ }
+ }
+
+ class RequestorActor(val computations: List<ActorRef>, val initLatch: CountDownLatch,
+ val stopLatch: CountDownLatch) : UntypedAbstractActor() {
+ private var received = 0
+
+ override fun onReceive(message: Any?) {
+ when (message) {
+ is Long -> {
+ if (++received >= ROUNDS) {
+ context.stop(self)
+ stopLatch.countDown()
+ } else {
+ computations[ThreadLocalRandom.current().nextInt(0, computations.size)]
+ .tell(ThreadLocalRandom.current().nextLong(), self)
+ }
+ }
+ else -> unhandled(message)
+ }
+ }
+
+ override fun preStart() {
+ initLatch.countDown()
+ }
+ }
+
+ class ComputationActor(val coefficients: LongArray, val initLatch: CountDownLatch) : UntypedAbstractActor() {
+ override fun onReceive(message: Any?) {
+ when (message) {
+ is Long -> {
+ var result = 0L
+ for (coefficient in coefficients) {
+ result += coefficient * message
+ }
+ sender.tell(result, self)
+ }
+ is Stop -> {
+ context.stop(self)
+ }
+ else -> unhandled(message)
+ }
+ }
+
+ override fun preStart() {
+ initLatch.countDown()
+ }
+ }
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/actors/StatefulActorBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/actors/StatefulActorBenchmark.kt
new file mode 100644
index 00000000..6968c895
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/actors/StatefulActorBenchmark.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.actors
+
+import benchmarks.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+/*
+ * kotlinx-based counterpart of [StatefulActorAkkaBenchmark]
+ *
+ * Benchmark (dispatcher) Mode Cnt Score Error Units
+ * StatefulActorBenchmark.multipleComputationsMultipleRequestors fjp avgt 10 81.649 ± 9.671 ms/op
+ * StatefulActorBenchmark.multipleComputationsMultipleRequestors ftp_1 avgt 10 160.590 ± 50.342 ms/op
+ * StatefulActorBenchmark.multipleComputationsMultipleRequestors ftp_8 avgt 10 275.798 ± 32.795 ms/op
+ *
+ * StatefulActorBenchmark.multipleComputationsSingleRequestor fjp avgt 10 67.206 ± 4.023 ms/op
+ * StatefulActorBenchmark.multipleComputationsSingleRequestor ftp_1 avgt 10 17.883 ± 1.314 ms/op
+ * StatefulActorBenchmark.multipleComputationsSingleRequestor ftp_8 avgt 10 77.052 ± 10.132 ms/op
+ *
+ * StatefulActorBenchmark.singleComputationMultipleRequestors fjp avgt 10 488.003 ± 53.014 ms/op
+ * StatefulActorBenchmark.singleComputationMultipleRequestors ftp_1 avgt 10 120.445 ± 24.659 ms/op
+ * StatefulActorBenchmark.singleComputationMultipleRequestors ftp_8 avgt 10 527.118 ± 51.139 ms/op
+ *
+ * StatefulActorBenchmark.singleComputationSingleRequestor fjp avgt 10 95.030 ± 23.850 ms/op
+ * StatefulActorBenchmark.singleComputationSingleRequestor ftp_1 avgt 10 16.005 ± 0.629 ms/op
+ * StatefulActorBenchmark.singleComputationSingleRequestor ftp_8 avgt 10 76.435 ± 5.076 ms/op
+ */
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class StatefulActorBenchmark : ParametrizedDispatcherBase() {
+
+ data class Letter(val message: Any, val sender: Channel<Letter>)
+
+ @Param("fjp", "ftp_1", "ftp_8", "experimental")
+ override var dispatcher: String = "fjp"
+
+ @Benchmark
+ fun singleComputationSingleRequestor() = runBlocking {
+ run(1, 1)
+ }
+
+ @Benchmark
+ fun singleComputationMultipleRequestors() = runBlocking {
+ run(1, CORES_COUNT)
+ }
+
+ @Benchmark
+ fun multipleComputationsSingleRequestor() = runBlocking {
+ run(CORES_COUNT, 1)
+ }
+
+ @Benchmark
+ fun multipleComputationsMultipleRequestors() = runBlocking {
+ run(CORES_COUNT, CORES_COUNT)
+ }
+
+ private suspend fun run(computationActorsCount: Int, requestorActorsCount: Int) {
+ val resultChannel: Channel<Unit> = Channel(requestorActorsCount)
+ val computations = (0 until computationActorsCount).map { computationActor() }
+ val requestors = (0 until requestorActorsCount).map { requestorActor(computations, resultChannel) }
+
+ for (requestor in requestors) {
+ requestor.send(Letter(1L, Channel()))
+ }
+
+ repeat(requestorActorsCount) {
+ resultChannel.receive()
+ }
+ }
+
+ private fun CoroutineScope.requestorActor(computations: List<SendChannel<Letter>>, stopChannel: Channel<Unit>) =
+ actor<Letter>(capacity = 1024) {
+ var received = 0
+ for (letter in channel) with(letter) {
+ when (message) {
+ is Long -> {
+ if (++received >= ROUNDS) {
+ stopChannel.send(Unit)
+ return@actor
+ } else {
+ computations[ThreadLocalRandom.current().nextInt(0, computations.size)]
+ .send(Letter(ThreadLocalRandom.current().nextLong(), channel))
+ }
+ }
+ else -> error("Cannot happen: $letter")
+ }
+ }
+ }
+}
+
+fun CoroutineScope.computationActor(stateSize: Int = STATE_SIZE) =
+ actor<StatefulActorBenchmark.Letter>(capacity = 1024) {
+ val coefficients = LongArray(stateSize) { ThreadLocalRandom.current().nextLong(0, 100) }
+
+ for (letter in channel) with(letter) {
+ when (message) {
+ is Long -> {
+ var result = 0L
+ for (coefficient in coefficients) {
+ result += message * coefficient
+ }
+
+ sender.send(StatefulActorBenchmark.Letter(result, channel))
+ }
+ is Stop -> return@actor
+ else -> error("Cannot happen: $letter")
+ }
+ }
+ }
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt
new file mode 100644
index 00000000..f6690977
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+open class FlatMapMergeBenchmark {
+
+ // Note: tests only absence of contention on downstream
+
+ @Param("10", "100", "1000")
+ private var iterations = 100
+
+ @Benchmark
+ fun flatMapUnsafe() = runBlocking {
+ benchmarks.flow.scrabble.flow {
+ repeat(iterations) { emit(it) }
+ }.flatMapMerge { value ->
+ flowOf(value)
+ }.collect {
+ if (it == -1) error("")
+ }
+ }
+
+ @Benchmark
+ fun flatMapSafe() = runBlocking {
+ kotlinx.coroutines.flow.flow {
+ repeat(iterations) { emit(it) }
+ }.flatMapMerge { value ->
+ flowOf(value)
+ }.collect {
+ if (it == -1) error("")
+ }
+ }
+
+} \ No newline at end of file
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt
new file mode 100644
index 00000000..e037069d
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+
+package benchmarks.flow
+
+import benchmarks.flow.scrabble.flow
+import io.reactivex.*
+import io.reactivex.functions.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+/*
+ * Results:
+ *
+ * // Throw FlowAborted overhead
+ * Numbers.primes avgt 7 3039.185 ± 25.598 us/op
+ * Numbers.primesRx avgt 7 2677.937 ± 17.720 us/op
+ *
+ * // On par
+ * Numbers.transformations avgt 7 16.207 ± 0.133 us/op
+ * Numbers.transformationsRx avgt 7 19.626 ± 0.135 us/op
+ *
+ * // Channels overhead
+ * Numbers.zip avgt 7 434.160 ± 7.014 us/op
+ * Numbers.zipRx avgt 7 87.898 ± 5.007 us/op
+ *
+ */
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+open class NumbersBenchmark {
+
+ companion object {
+ private const val primes = 100
+ private const val natural = 1000
+ }
+
+ private fun numbers() = flow {
+ for (i in 2L..Long.MAX_VALUE) emit(i)
+ }
+
+ private fun primesFlow(): Flow<Long> = flow {
+ var source = numbers()
+ while (true) {
+ val next = source.take(1).single()
+ emit(next)
+ source = source.filter { it % next != 0L }
+ }
+ }
+
+ private fun rxNumbers() =
+ Flowable.generate(Callable { 1L }, BiFunction<Long, Emitter<Long>, Long> { state, emitter ->
+ val newState = state + 1
+ emitter.onNext(newState)
+ newState
+ })
+
+ private fun generateRxPrimes(): Flowable<Long> = Flowable.generate(Callable { rxNumbers() },
+ BiFunction<Flowable<Long>, Emitter<Long>, Flowable<Long>> { state, emitter ->
+ // Not the most fair comparison, but here we go
+ val prime = state.firstElement().blockingGet()
+ emitter.onNext(prime)
+ state.filter { it % prime != 0L }
+ })
+
+ @Benchmark
+ fun primes() = runBlocking {
+ primesFlow().take(primes).count()
+ }
+
+ @Benchmark
+ fun primesRx() = generateRxPrimes().take(primes.toLong()).count().blockingGet()
+
+ @Benchmark
+ fun zip() = runBlocking {
+ val numbers = numbers().take(natural)
+ val first = numbers
+ .filter { it % 2L != 0L }
+ .map { it * it }
+ val second = numbers
+ .filter { it % 2L == 0L }
+ .map { it * it }
+ first.zip(second) { v1, v2 -> v1 + v2 }.filter { it % 3 == 0L }.count()
+ }
+
+ @Benchmark
+ fun zipRx() {
+ val numbers = rxNumbers().take(natural.toLong())
+ val first = numbers
+ .filter { it % 2L != 0L }
+ .map { it * it }
+ val second = numbers
+ .filter { it % 2L == 0L }
+ .map { it * it }
+ first.zipWith(second, BiFunction<Long, Long, Long> { v1, v2 -> v1 + v2 }).filter { it % 3 == 0L }.count()
+ .blockingGet()
+ }
+
+ @Benchmark
+ fun transformations(): Int = runBlocking {
+ numbers()
+ .take(natural)
+ .filter { it % 2L != 0L }
+ .map { it * it }
+ .filter { (it + 1) % 3 == 0L }.count()
+ }
+
+ @Benchmark
+ fun transformationsRx(): Long {
+ return rxNumbers().take(natural.toLong())
+ .filter { it % 2L != 0L }
+ .map { it * it }
+ .filter { (it + 1) % 3 == 0L }.count()
+ .blockingGet()
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt
new file mode 100644
index 00000000..f8c459fd
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+import benchmarks.flow.scrabble.flow as unsafeFlow
+import kotlinx.coroutines.flow.flow as safeFlow
+
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+open class SafeFlowBenchmark {
+
+ private fun numbersSafe() = safeFlow {
+ for (i in 2L..1000L) emit(i)
+ }
+
+ private fun numbersUnsafe() = unsafeFlow {
+ for (i in 2L..1000L) emit(i)
+ }
+
+ @Benchmark
+ fun safeNumbers(): Int = runBlocking {
+ numbersSafe().count()
+ }
+
+ @Benchmark
+ fun unsafeNumbers(): Int = runBlocking {
+ numbersUnsafe().count()
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt
new file mode 100644
index 00000000..b556053b
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.openjdk.jmh.annotations.*
+import java.lang.Long.*
+import java.lang.Long.max
+import java.util.*
+import java.util.concurrent.*
+import kotlin.math.*
+
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class FlowPlaysScrabbleBase : ShakespearePlaysScrabble() {
+
+ @Benchmark
+ public override fun play(): List<Map.Entry<Int, List<String>>> {
+ val scoreOfALetter = { letter: Int -> flowOf(letterScores[letter - 'a'.toInt()]) }
+
+ val letterScore = { entry: Map.Entry<Int, LongWrapper> ->
+ flowOf(
+ letterScores[entry.key - 'a'.toInt()] * Integer.min(
+ entry.value.get().toInt(),
+ scrabbleAvailableLetters[entry.key - 'a'.toInt()]
+ )
+ )
+ }
+
+ val toIntegerStream = { string: String ->
+ IterableSpliterator.of(string.chars().boxed().spliterator()).asFlow()
+ }
+
+ val histoOfLetters = { word: String ->
+ flow {
+ emit(toIntegerStream(word).fold(HashMap<Int, LongWrapper>()) { accumulator, value ->
+ var newValue: LongWrapper? = accumulator[value]
+ if (newValue == null) {
+ newValue = LongWrapper.zero()
+ }
+ accumulator[value] = newValue.incAndSet()
+ accumulator
+ })
+ }
+ }
+
+ val blank = { entry: Map.Entry<Int, LongWrapper> ->
+ flowOf(max(0L, entry.value.get() - scrabbleAvailableLetters[entry.key - 'a'.toInt()]))
+ }
+
+ val nBlanks = { word: String ->
+ flow {
+ emit(histoOfLetters(word)
+ .flatMapConcat { map -> map.entries.iterator().asFlow() }
+ .flatMapConcat({ blank(it) })
+ .reduce { a, b -> a + b })
+ }
+ }
+
+ val checkBlanks = { word: String ->
+ nBlanks(word).flatMapConcat { l -> flowOf(l <= 2L) }
+ }
+
+ val score2 = { word: String ->
+ flow {
+ emit(histoOfLetters(word)
+ .flatMapConcat { map -> map.entries.iterator().asFlow() }
+ .flatMapConcat { letterScore(it) }
+ .reduce { a, b -> a + b })
+ }
+ }
+
+ val first3 = { word: String ->
+ IterableSpliterator.of(word.chars().boxed().limit(3).spliterator()).asFlow()
+ }
+
+ val last3 = { word: String ->
+ IterableSpliterator.of(word.chars().boxed().skip(3).spliterator()).asFlow()
+ }
+
+ val toBeMaxed = { word: String -> flowOf(first3(word), last3(word)).flattenConcat() }
+
+ // Bonus for double letter
+ val bonusForDoubleLetter = { word: String ->
+ flow {
+ emit(toBeMaxed(word)
+ .flatMapConcat { scoreOfALetter(it) }
+ .reduce { a, b -> max(a, b) })
+ }
+ }
+
+ val score3 = { word: String ->
+ flow {
+ emit(flowOf(
+ score2(word), score2(word),
+ bonusForDoubleLetter(word),
+ bonusForDoubleLetter(word),
+ flowOf(if (word.length == 7) 50 else 0)
+ ).flattenConcat().reduce { a, b -> a + b })
+ }
+ }
+
+ val buildHistoOnScore: (((String) -> Flow<Int>) -> Flow<TreeMap<Int, List<String>>>) = { score ->
+ flow {
+ emit(shakespeareWords.asFlow()
+ .filter({ scrabbleWords.contains(it) && checkBlanks(it).single() })
+ .fold(TreeMap<Int, List<String>>(Collections.reverseOrder())) { acc, value ->
+ val key = score(value).single()
+ var list = acc[key] as MutableList<String>?
+ if (list == null) {
+ list = ArrayList()
+ acc[key] = list
+ }
+ list.add(value)
+ acc
+ })
+ }
+ }
+
+ return runBlocking {
+ buildHistoOnScore(score3)
+ .flatMapConcat { map -> map.entries.iterator().asFlow() }
+ .take(3)
+ .toList()
+ }
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt
new file mode 100644
index 00000000..921f390d
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. and contributors Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.openjdk.jmh.annotations.*
+import java.lang.Long.max
+import java.util.*
+import java.util.concurrent.*
+import java.util.stream.*
+import kotlin.math.*
+
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class FlowPlaysScrabbleOpt : ShakespearePlaysScrabble() {
+
+ @Benchmark
+ public override fun play(): List<Map.Entry<Int, List<String>>> {
+ val histoOfLetters = { word: String ->
+ flow {
+ emit(word.asFlow().fold(HashMap<Int, MutableLong>()) { accumulator, value ->
+ var newValue: MutableLong? = accumulator[value]
+ if (newValue == null) {
+ newValue = MutableLong()
+ accumulator[value] = newValue
+ }
+ newValue.incAndSet()
+ accumulator
+ })
+ }
+ }
+
+ val blank = { entry: Map.Entry<Int, MutableLong> ->
+ max(0L, entry.value.get() - scrabbleAvailableLetters[entry.key - 'a'.toInt()])
+ }
+
+ val nBlanks = { word: String ->
+ flow {
+ emit(histoOfLetters(word)
+ .flatMapConcatIterable { it.entries }
+ .map({ blank(it) })
+ .sum()
+ )
+ }
+ }
+
+ val checkBlanks = { word: String ->
+ nBlanks(word).map { it <= 2L }
+ }
+
+ val letterScore = { entry: Map.Entry<Int, MutableLong> ->
+ letterScores[entry.key - 'a'.toInt()] * Integer.min(
+ entry.value.get().toInt(),
+ scrabbleAvailableLetters[entry.key - 'a'.toInt()]
+ )
+ }
+
+ val score2 = { word: String ->
+ flow {
+ emit(histoOfLetters(word)
+ .flatMapConcatIterable { it.entries }
+ .map { letterScore(it) }
+ .sum())
+ }
+ }
+
+ val first3 = { word: String -> word.asFlow(endIndex = 3) }
+ val last3 = { word: String -> word.asFlow(startIndex = 3) }
+ val toBeMaxed = { word: String -> concat(first3(word), last3(word)) }
+
+ val bonusForDoubleLetter = { word: String ->
+ flow {
+ emit(toBeMaxed(word)
+ .map { letterScores[it.toInt() - 'a'.toInt()] }
+ .max())
+ }
+ }
+
+ val score3 = { word: String ->
+ flow {
+ val sum = score2(word).single() + bonusForDoubleLetter(word).single()
+ emit(sum * 2 + if (word.length == 7) 50 else 0)
+ }
+ }
+
+ val buildHistoOnScore: (((String) -> Flow<Int>) -> Flow<TreeMap<Int, List<String>>>) = { score ->
+ flow {
+ emit(shakespeareWords.asFlow()
+ .filter({ scrabbleWords.contains(it) && checkBlanks(it).single() })
+ .fold(TreeMap<Int, List<String>>(Collections.reverseOrder())) { acc, value ->
+ val key = score(value).single()
+ var list = acc[key] as MutableList<String>?
+ if (list == null) {
+ list = ArrayList()
+ acc[key] = list
+ }
+ list.add(value)
+ acc
+ })
+ }
+ }
+
+ return runBlocking {
+ buildHistoOnScore(score3)
+ .flatMapConcatIterable { it.entries }
+ .take(3)
+ .toList()
+ }
+ }
+}
+
+public fun String.asFlow() = flow {
+ forEach {
+ emit(it.toInt())
+ }
+}
+
+public fun String.asFlow(startIndex: Int = 0, endIndex: Int = length) =
+ StringByCharFlow(this, startIndex, endIndex.coerceAtMost(this.length))
+
+public suspend inline fun Flow<Int>.sum(): Int {
+ val collector = object : FlowCollector<Int> {
+ public var sum = 0
+
+ override suspend fun emit(value: Int) {
+ sum += value
+ }
+ }
+ collect(collector)
+ return collector.sum
+}
+
+public suspend inline fun Flow<Int>.max(): Int {
+ val collector = object : FlowCollector<Int> {
+ public var max = 0
+
+ override suspend fun emit(value: Int) {
+ max = max(max, value)
+ }
+ }
+ collect(collector)
+ return collector.max
+}
+
+@JvmName("longSum")
+public suspend inline fun Flow<Long>.sum(): Long {
+ val collector = object : FlowCollector<Long> {
+ public var sum = 0L
+
+ override suspend fun emit(value: Long) {
+ sum += value
+ }
+ }
+ collect(collector)
+ return collector.sum
+}
+
+public class StringByCharFlow(private val source: String, private val startIndex: Int, private val endIndex: Int): Flow<Char> {
+ override suspend fun collect(collector: FlowCollector<Char>) {
+ for (i in startIndex until endIndex) collector.emit(source[i])
+ }
+}
+
+public fun <T> concat(first: Flow<T>, second: Flow<T>): Flow<T> = flow {
+ first.collect { value ->
+ return@collect emit(value)
+ }
+
+ second.collect { value ->
+ return@collect emit(value)
+ }
+}
+
+public fun <T, R> Flow<T>.flatMapConcatIterable(transformer: (T) -> Iterable<R>): Flow<R> = flow {
+ collect { value ->
+ transformer(value).forEach { r ->
+ emit(r)
+ }
+ }
+}
+
+public inline fun <T> flow(@BuilderInference crossinline block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
+ return object : Flow<T> {
+ override suspend fun collect(collector: FlowCollector<T>) {
+ collector.block()
+ }
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt
new file mode 100644
index 00000000..434ea1e1
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble
+
+import java.util.*
+
+object IterableSpliterator {
+ @JvmStatic
+ public fun <T> of(spliterator: Spliterator<T>): Iterable<T> = Iterable { Spliterators.iterator(spliterator) }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/README.md b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/README.md
new file mode 100644
index 00000000..13e016fd
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/README.md
@@ -0,0 +1,42 @@
+## Reactive scrabble benchmarks
+
+This package contains reactive scrabble benchmarks.
+
+Reactive Scrabble benchmarks were originally developed by José Paumard and are [available](https://github.com/JosePaumard/jdk8-stream-rx-comparison-reloaded) under Apache 2.0,
+Flow version is adaptation of this work.
+All Rx and Reactive benchmarks are based on (or copied from) [David Karnok work](https://github.com/akarnokd/akarnokd-misc).
+
+### Benchmark classes
+
+The package (split into two sourcesets, `kotlin` and `java`), contains different benchmarks with different purposes
+
+ * `RxJava2PlaysScrabble` and `RxJava2PlaysScrabbleOpt` are copied as is and used for comparison. The infrastructure (e.g. `FlowableSplit`)
+ is copied from `akarnokd-misc` in order for the latter benchmark to work.
+ This is the original benchmark for `RxJava`.
+ * `ReactorPlaysScrabble` is an original benchmark for `Reactor`, but rewritten into Kotlin.
+ It is disabled by default and had the only purpose -- verify that Kotlin version performs as the original Java version
+ (which could have been different due to lambdas translation, implicit boxing, etc.). It is disabled because
+ it has almost no difference compared to `RxJava` benchmark.
+ * `FlowPlaysScrabbleBase` is a scrabble benchmark rewritten on top of the `Flow` API without using any optimizations or tricky internals.
+ * `FlowPlaysScrabbleOpt` is an optimized version of benchmark that follows the same guidelines as `RxJava2PlaysScrabbleOpt`: it still is
+ lazy, reactive and uses only `Flow` abstraction.
+ * `SequencePlaysScrabble` is a version of benchmark built on top of `Sequence` without suspensions, used as a lower bound.
+ * `SaneFlowPlaysScrabble` is a `SequencePlaysScrabble` that produces `Flow`.
+ This benchmark is not identical (in terms of functions pipelining) to `FlowPlaysScrabbleOpt`, but rather is used as a lower bound of `Flow` performance
+ on this particular task.
+
+### Results
+
+Benchmark results for throughput mode, Java `1.8.162`.
+Full command: `taskset -c 0,1 java -jar benchmarks.jar -f 2 -jvmArgsPrepend "-XX:+UseParallelGC" .*Scrabble.*`.
+
+```
+FlowPlaysScrabbleBase.play avgt 14 94.845 ± 1.345 ms/op
+FlowPlaysScrabbleOpt.play avgt 14 20.587 ± 0.173 ms/op
+
+RxJava2PlaysScrabble.play avgt 14 114.253 ± 3.450 ms/op
+RxJava2PlaysScrabbleOpt.play avgt 14 30.795 ± 0.144 ms/op
+
+SaneFlowPlaysScrabble.play avgt 14 18.825 ± 0.231 ms/op
+SequencePlaysScrabble.play avgt 14 13.787 ± 0.111 ms/op
+```
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt
new file mode 100644
index 00000000..9adc4f1f
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble
+
+import org.openjdk.jmh.annotations.*
+import reactor.core.publisher.*
+import java.lang.Long.*
+import java.util.*
+import java.util.concurrent.*
+import java.util.function.Function
+
+/*@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)*/
+open class ReactorPlaysScrabble : ShakespearePlaysScrabble() {
+
+// @Benchmark
+ public override fun play(): List<Map.Entry<Int, List<String>>> {
+ val scoreOfALetter = Function<Int, Flux<Int>> { letter -> Flux.just(letterScores[letter - 'a'.toInt()]) }
+
+ val letterScore = Function<Map.Entry<Int, LongWrapper>, Flux<Int>> { entry ->
+ Flux.just(
+ letterScores[entry.key - 'a'.toInt()] * Integer.min(
+ entry.value.get().toInt(),
+ scrabbleAvailableLetters[entry.key - 'a'.toInt()]
+ )
+ )
+ }
+
+ val toIntegerStream = Function<String, Flux<Int>> { string ->
+ Flux.fromIterable(IterableSpliterator.of(string.chars().boxed().spliterator()))
+ }
+
+ val histoOfLetters = Function<String, Flux<HashMap<Int, LongWrapper>>> { word ->
+ Flux.from(toIntegerStream.apply(word)
+ .collect(
+ { HashMap() },
+ { map: HashMap<Int, LongWrapper>, value: Int ->
+ var newValue: LongWrapper? = map[value]
+ if (newValue == null) {
+ newValue = LongWrapper.zero()
+ }
+ map[value] = newValue.incAndSet()
+ }
+
+ ))
+ }
+
+ val blank = Function<Map.Entry<Int, LongWrapper>, Flux<Long>> { entry ->
+ Flux.just(max(0L, entry.value.get() - scrabbleAvailableLetters[entry.key - 'a'.toInt()]))
+ }
+
+ val nBlanks = Function<String, Flux<Long>> { word ->
+ Flux.from(histoOfLetters.apply(word)
+ .flatMap<Map.Entry<Int, LongWrapper>> { map -> Flux.fromIterable<Map.Entry<Int, LongWrapper>>(Iterable { map.entries.iterator() }) }
+ .flatMap(blank)
+ .reduce { a, b -> sum(a, b) })
+ }
+
+ val checkBlanks = Function<String, Flux<Boolean>> { word ->
+ nBlanks.apply(word)
+ .flatMap { l -> Flux.just(l <= 2L) }
+ }
+
+
+ val score2 = Function<String, Flux<Int>> { word ->
+ Flux.from(histoOfLetters.apply(word)
+ .flatMap<Map.Entry<Int, LongWrapper>> { map -> Flux.fromIterable<Map.Entry<Int, LongWrapper>>(Iterable { map.entries.iterator() }) }
+ .flatMap(letterScore)
+ .reduce { a, b -> Integer.sum(a, b) })
+
+ }
+
+ val first3 = Function<String, Flux<Int>> { word -> Flux.fromIterable(
+ IterableSpliterator.of(
+ word.chars().boxed().limit(3).spliterator()
+ )
+ ) }
+ val last3 = Function<String, Flux<Int>> { word -> Flux.fromIterable(
+ IterableSpliterator.of(
+ word.chars().boxed().skip(3).spliterator()
+ )
+ ) }
+
+ val toBeMaxed = Function<String, Flux<Int>> { word ->
+ Flux.just(first3.apply(word), last3.apply(word))
+ .flatMap { Stream -> Stream }
+ }
+
+ // Bonus for double letter
+ val bonusForDoubleLetter = Function<String, Flux<Int>> { word ->
+ Flux.from<Int>(toBeMaxed.apply(word)
+ .flatMap<Int>(scoreOfALetter)
+ .reduce { a, b -> Integer.max(a, b) }
+ )
+ }
+
+ val score3 = Function<String, Flux<Int>> { word ->
+ Flux.from(Flux.just(
+ score2.apply(word),
+ score2.apply(word),
+ bonusForDoubleLetter.apply(word),
+ bonusForDoubleLetter.apply(word),
+ Flux.just(if (word.length == 7) 50 else 0)
+ )
+ .flatMap { Stream -> Stream }
+ .reduce { a, b -> Integer.sum(a, b) })
+ }
+
+ val buildHistoOnScore = Function<Function<String, Flux<Int>>, Flux<TreeMap<Int, List<String>>>> { score ->
+ Flux.from(Flux.fromIterable(Iterable { shakespeareWords.iterator() })
+ .filter( { scrabbleWords.contains(it) })
+ .filter({ word -> checkBlanks.apply(word).toIterable().iterator().next() })
+ .collect(
+ { TreeMap<Int, List<String>>(Collections.reverseOrder()) },
+ { map: TreeMap<Int, List<String>>, word: String ->
+ val key = score.apply(word).toIterable().iterator().next()
+ var list = map[key] as MutableList<String>?
+ if (list == null) {
+ list = ArrayList()
+ map[key] = list
+ }
+ list.add(word)
+ }
+ ))
+ }
+
+ val finalList2 = Flux.from<ArrayList<Map.Entry<Int, List<String>>>>(buildHistoOnScore.apply(score3)
+ .flatMap<Map.Entry<Int, List<String>>> { map -> Flux.fromIterable<Map.Entry<Int, List<String>>>(Iterable { map.entries.iterator() }) }
+ .take(3)
+ .collect<ArrayList<Map.Entry<Int, List<String>>>>(
+ { ArrayList() },
+ { list, entry -> list.add(entry) }
+ )
+ ).toIterable().iterator().next()
+
+ return finalList2
+ }
+
+}
+
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt
new file mode 100644
index 00000000..597667c1
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.openjdk.jmh.annotations.*
+import java.lang.Long.*
+import java.util.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class SaneFlowPlaysScrabble : ShakespearePlaysScrabble() {
+
+ @Benchmark
+ public override fun play(): List<Map.Entry<Int, List<String>>> {
+ val score3: suspend (String) -> Int = { word: String ->
+ val sum = score2(word) + bonusForDoubleLetter(word)
+ sum * 2 + if (word.length == 7) 50 else 0
+ }
+
+ val buildHistoOnScore: ((suspend (String) -> Int) -> Flow<TreeMap<Int, List<String>>>) = { score ->
+ flow {
+ emit(shakespeareWords.asFlow()
+ .filter({ scrabbleWords.contains(it) && checkBlanks(it) })
+ .fold(TreeMap<Int, List<String>>(Collections.reverseOrder())) { acc, value ->
+ val key = score(value)
+ var list = acc[key] as MutableList<String>?
+ if (list == null) {
+ list = ArrayList()
+ acc[key] = list
+ }
+ list.add(value)
+ acc
+ })
+ }
+ }
+
+ return runBlocking {
+ buildHistoOnScore(score3)
+ .flatMapConcatIterable { it.entries }
+ .take(3)
+ .toList()
+ }
+ }
+
+ private suspend inline fun score2(word: String): Int {
+ return buildHistogram(word)
+ .map { it.letterScore() }
+ .sum()
+ }
+
+ private suspend inline fun bonusForDoubleLetter(word: String): Int {
+ return toBeMaxed(word)
+ .map { letterScores[it - 'a'.toInt()] }
+ .max()
+ }
+
+ private fun Map.Entry<Int, MutableLong>.letterScore(): Int = letterScores[key - 'a'.toInt()] * Integer.min(
+ value.get().toInt(),
+ scrabbleAvailableLetters[key - 'a'.toInt()])
+
+ private fun toBeMaxed(word: String) = concat(word.asSequence(), word.asSequence(endIndex = 3))
+
+ private suspend inline fun checkBlanks(word: String) = numBlanks(word) <= 2L
+
+ private suspend fun numBlanks(word: String): Long {
+ return buildHistogram(word)
+ .map { blanks(it) }
+ .sum()
+ }
+
+ private fun blanks(entry: Map.Entry<Int, MutableLong>): Long =
+ max(0L, entry.value.get() - scrabbleAvailableLetters[entry.key - 'a'.toInt()])
+
+ private suspend inline fun buildHistogram(word: String): HashMap<Int, MutableLong> {
+ return word.asSequence().fold(HashMap()) { accumulator, value ->
+ var newValue: MutableLong? = accumulator[value]
+ if (newValue == null) {
+ newValue = MutableLong()
+ accumulator[value] = newValue
+ }
+ newValue.incAndSet()
+ accumulator
+ }
+ }
+
+ private fun String.asSequence(startIndex: Int = 0, endIndex: Int = length) = flow {
+ for (i in startIndex until endIndex.coerceAtMost(length)) {
+ emit(get(i).toInt())
+ }
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
new file mode 100644
index 00000000..5f4f4c2d
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.openjdk.jmh.annotations.*
+import java.lang.Long.*
+import java.util.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+open class SequencePlaysScrabble : ShakespearePlaysScrabble() {
+
+ @Benchmark
+ public override fun play(): List<Map.Entry<Int, List<String>>> {
+ val score2: (String) -> Int = { word: String ->
+ buildHistogram(word)
+ .map { it.letterScore() }
+ .sum()
+ }
+
+ val bonusForDoubleLetter: (String) -> Int = { word: String ->
+ toBeMaxed(word)
+ .map { letterScores[it - 'a'.toInt()] }
+ .max()!!
+ }
+
+ val score3: (String) -> Int = { word: String ->
+ val sum = score2(word) + bonusForDoubleLetter(word)
+ sum * 2 + if (word.length == 7) 50 else 0
+ }
+
+ val buildHistoOnScore: (((String) -> Int) -> Flow<TreeMap<Int, List<String>>>) = { score ->
+ flow {
+ emit(shakespeareWords.asSequence()
+ .filter({ scrabbleWords.contains(it) && checkBlanks(it) })
+ .fold(TreeMap<Int, List<String>>(Collections.reverseOrder())) { acc, value ->
+ val key = score(value)
+ var list = acc[key] as MutableList<String>?
+ if (list == null) {
+ list = ArrayList()
+ acc[key] = list
+ }
+ list.add(value)
+ acc
+ })
+ }
+ }
+
+ return runBlocking {
+ buildHistoOnScore(score3)
+ .flatMapConcatIterable { it.entries }
+ .take(3)
+ .toList()
+ }
+ }
+
+ private fun Map.Entry<Int, MutableLong>.letterScore(): Int = letterScores[key - 'a'.toInt()] * Integer.min(
+ value.get().toInt(),
+ scrabbleAvailableLetters[key - 'a'.toInt()])
+
+ private fun toBeMaxed(word: String) = word.asSequence(startIndex = 3) + word.asSequence(endIndex = 3)
+
+ private fun checkBlanks(word: String) = numBlanks(word) <= 2L
+
+ private fun numBlanks(word: String): Long {
+ return buildHistogram(word)
+ .map { blanks(it) }
+ .sum()
+ }
+
+ private fun blanks(entry: Map.Entry<Int, MutableLong>): Long =
+ max(0L, entry.value.get() - scrabbleAvailableLetters[entry.key - 'a'.toInt()])
+
+ private fun buildHistogram(word: String): HashMap<Int, MutableLong> {
+ return word.asSequence().fold(HashMap()) { accumulator, value ->
+ var newValue: MutableLong? = accumulator[value]
+ if (newValue == null) {
+ newValue = MutableLong()
+ accumulator[value] = newValue
+ }
+ newValue.incAndSet()
+ accumulator
+ }
+ }
+
+ private fun String.asSequence(startIndex: Int = 0, endIndex: Int = length) = object : Sequence<Int> {
+ override fun iterator(): Iterator<Int> = object : Iterator<Int> {
+ private val _endIndex = endIndex.coerceAtMost(length)
+ private var currentIndex = startIndex
+ override fun hasNext(): Boolean = currentIndex < _endIndex
+ override fun next(): Int = get(currentIndex++).toInt()
+ }
+ }
+}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
new file mode 100644
index 00000000..7eaa3f0a
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks.flow.scrabble
+
+import org.openjdk.jmh.annotations.*
+import java.io.*
+import java.util.stream.*
+import java.util.zip.*
+
+@State(Scope.Benchmark)
+abstract class ShakespearePlaysScrabble {
+ @Throws(Exception::class)
+ abstract fun play(): List<Map.Entry<Int, List<String>>>
+
+ public class MutableLong {
+ var value: Long = 0
+ fun get(): Long {
+ return value
+ }
+
+ fun incAndSet(): MutableLong {
+ value++
+ return this
+ }
+
+ fun add(other: MutableLong): MutableLong {
+ value += other.value
+ return this
+ }
+ }
+
+ public interface LongWrapper {
+ fun get(): Long
+
+ @JvmDefault
+ fun incAndSet(): LongWrapper {
+ return object : LongWrapper {
+ override fun get(): Long = this@LongWrapper.get() + 1L
+ }
+ }
+
+ @JvmDefault
+ fun add(other: LongWrapper): LongWrapper {
+ return object : LongWrapper {
+ override fun get(): Long = this@LongWrapper.get() + other.get()
+ }
+ }
+
+ companion object {
+ fun zero(): LongWrapper {
+ return object : LongWrapper {
+ override fun get(): Long = 0L
+ }
+ }
+ }
+ }
+
+ @JvmField
+ public val letterScores: IntArray = intArrayOf(1, 3, 3, 2, 1, 4, 2, 4, 1, 8, 5, 1, 3, 1, 1, 3, 10, 1, 1, 1, 1, 4, 4, 8, 4, 10)
+
+ @JvmField
+ public val scrabbleAvailableLetters: IntArray =
+ intArrayOf(9, 2, 2, 1, 12, 2, 3, 2, 9, 1, 1, 4, 2, 6, 8, 2, 1, 6, 4, 6, 4, 2, 2, 1, 2, 1)
+
+ @JvmField
+ public val scrabbleWords: Set<String> = readResource("ospd.txt.gz")
+
+ @JvmField
+ public val shakespeareWords: Set<String> = readResource("words.shakespeare.txt.gz")
+
+ private fun readResource(path: String) =
+ BufferedReader(InputStreamReader(GZIPInputStream(this.javaClass.classLoader.getResourceAsStream(path)))).lines()
+ .map { it.toLowerCase() }.collect(Collectors.toSet())
+
+ init {
+ val expected = listOf(120 to listOf("jezebel", "quickly"),
+ 118 to listOf("zephyrs"), 116 to listOf("equinox"))
+ val actual = play().map { it.key to it.value }
+ if (expected != actual) {
+ error("Incorrect benchmark, output: $actual")
+ }
+ }
+}
diff --git a/benchmarks/src/jmh/resources/ospd.txt.gz b/benchmarks/src/jmh/resources/ospd.txt.gz
new file mode 100644
index 00000000..8a3074e9
--- /dev/null
+++ b/benchmarks/src/jmh/resources/ospd.txt.gz
Binary files differ
diff --git a/benchmarks/src/jmh/resources/words.shakespeare.txt.gz b/benchmarks/src/jmh/resources/words.shakespeare.txt.gz
new file mode 100644
index 00000000..456f56cb
--- /dev/null
+++ b/benchmarks/src/jmh/resources/words.shakespeare.txt.gz
Binary files differ
diff --git a/binary-compatibility-validator/README.md b/binary-compatibility-validator/README.md
new file mode 100644
index 00000000..e34d6877
--- /dev/null
+++ b/binary-compatibility-validator/README.md
@@ -0,0 +1,10 @@
+# Public API binary compatibility validator
+
+This module allows to dump and compare public binary API to ensure binary compatibility with a previous version.
+This tool is slightly adapted copy of [original Kotlin compatibility validator](https://github.com/JetBrains/kotlin/tree/master/libraries/tools/binary-compatibility-validator) by @ilya-g.
+
+To update public API dumps use:
+
+```bash
+./gradlew :binary-compatibility-validator:test -Poverwrite.output=true
+```
diff --git a/binary-compatibility-validator/build.gradle b/binary-compatibility-validator/build.gradle
new file mode 100644
index 00000000..c6eaffdf
--- /dev/null
+++ b/binary-compatibility-validator/build.gradle
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+configurations {
+ testArtifacts
+ configureKotlinJvmPlatform(testArtifacts)
+}
+
+dependencies {
+ compile 'org.ow2.asm:asm-debug-all:5.0.4'
+ compile 'com.google.code.gson:gson:2.6.2'
+
+ testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
+
+ testArtifacts project(':kotlinx-coroutines-core')
+ testArtifacts project(':kotlinx-coroutines-test')
+ testArtifacts project(':kotlinx-coroutines-debug')
+
+ testArtifacts project(':kotlinx-coroutines-reactive')
+ testArtifacts project(':kotlinx-coroutines-reactor')
+ testArtifacts project(':kotlinx-coroutines-rx2')
+
+ testArtifacts project(':kotlinx-coroutines-guava')
+ testArtifacts project(':kotlinx-coroutines-jdk8')
+ testArtifacts project(':kotlinx-coroutines-slf4j')
+ testArtifacts project(path: ':kotlinx-coroutines-play-services', configuration: 'default')
+
+ testArtifacts project(':kotlinx-coroutines-android')
+ testArtifacts project(':kotlinx-coroutines-javafx')
+ testArtifacts project(':kotlinx-coroutines-swing')
+}
+
+def testCasesDeclarationsDump = "${buildDir}/visibilities.json".toString()
+
+compileTestKotlin {
+ kotlinOptions {
+ freeCompilerArgs = ["-Xdump-declarations-to=$testCasesDeclarationsDump"]
+ }
+}
+
+sourceSets {
+ test {
+ java {
+ srcDir "test/cases"
+ }
+ }
+}
+
+test {
+ dependsOn cleanCompileTestKotlin
+ dependsOn configurations.testArtifacts
+
+ systemProperty 'testCasesClassesDirs', sourceSets.test.output.classesDirs.asPath
+ systemProperty 'testCasesDeclarations', testCasesDeclarationsDump
+ systemProperty 'overwrite.output', project.properties['overwrite.output']
+ jvmArgs '-ea'
+}
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-android.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-android.txt
new file mode 100644
index 00000000..b97d8462
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-android.txt
@@ -0,0 +1,13 @@
+public abstract class kotlinx/coroutines/android/HandlerDispatcher : kotlinx/coroutines/MainCoroutineDispatcher, kotlinx/coroutines/Delay {
+ public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun getImmediate ()Lkotlinx/coroutines/android/HandlerDispatcher;
+ public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/DisposableHandle;
+}
+
+public final class kotlinx/coroutines/android/HandlerDispatcherKt {
+ public static final fun awaitFrame (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun from (Landroid/os/Handler;)Lkotlinx/coroutines/android/HandlerDispatcher;
+ public static final fun from (Landroid/os/Handler;Ljava/lang/String;)Lkotlinx/coroutines/android/HandlerDispatcher;
+ public static synthetic fun from$default (Landroid/os/Handler;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/android/HandlerDispatcher;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt
new file mode 100644
index 00000000..8a4cf58c
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt
@@ -0,0 +1,1168 @@
+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 final fun getContext ()Lkotlin/coroutines/CoroutineContext;
+ public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
+ public fun isActive ()Z
+ 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 {
+ public static final fun awaitAll (Ljava/util/Collection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitAll ([Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun joinAll (Ljava/util/Collection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun joinAll ([Lkotlinx/coroutines/Job;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/BuildersKt {
+ public static final fun async (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Deferred;
+ public static synthetic fun async$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Deferred;
+ public static final fun invoke (Lkotlinx/coroutines/CoroutineDispatcher;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun launch (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
+ public static synthetic fun launch$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
+ public static final fun runBlocking (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static synthetic fun runBlocking$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object;
+ public static final fun withContext (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/coroutines/CancellableContinuation : kotlin/coroutines/Continuation {
+ public abstract fun cancel (Ljava/lang/Throwable;)Z
+ public abstract fun completeResume (Ljava/lang/Object;)V
+ public abstract synthetic fun initCancellability ()V
+ public abstract fun invokeOnCancellation (Lkotlin/jvm/functions/Function1;)V
+ public abstract fun isActive ()Z
+ public abstract fun isCancelled ()Z
+ public abstract fun isCompleted ()Z
+ public abstract fun resume (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
+ public abstract fun resumeUndispatched (Lkotlinx/coroutines/CoroutineDispatcher;Ljava/lang/Object;)V
+ public abstract fun resumeUndispatchedWithException (Lkotlinx/coroutines/CoroutineDispatcher;Ljava/lang/Throwable;)V
+ public abstract fun tryResume (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+ public abstract fun tryResumeWithException (Ljava/lang/Throwable;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/CancellableContinuation$DefaultImpls {
+ public static synthetic fun cancel$default (Lkotlinx/coroutines/CancellableContinuation;Ljava/lang/Throwable;ILjava/lang/Object;)Z
+ public static synthetic fun tryResume$default (Lkotlinx/coroutines/CancellableContinuation;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object;
+}
+
+public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/CancellableContinuation {
+ public fun <init> (Lkotlin/coroutines/Continuation;I)V
+ public fun cancel (Ljava/lang/Throwable;)Z
+ public fun completeResume (Ljava/lang/Object;)V
+ public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
+ public fun getContext ()Lkotlin/coroutines/CoroutineContext;
+ public fun getContinuationCancellationCause (Lkotlinx/coroutines/Job;)Ljava/lang/Throwable;
+ public final fun getResult ()Ljava/lang/Object;
+ public fun getStackTraceElement ()Ljava/lang/StackTraceElement;
+ public synthetic fun initCancellability ()V
+ public fun invokeOnCancellation (Lkotlin/jvm/functions/Function1;)V
+ public fun isActive ()Z
+ public fun isCancelled ()Z
+ public fun isCompleted ()Z
+ protected fun nameString ()Ljava/lang/String;
+ public fun resume (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
+ public fun resumeUndispatched (Lkotlinx/coroutines/CoroutineDispatcher;Ljava/lang/Object;)V
+ public fun resumeUndispatchedWithException (Lkotlinx/coroutines/CoroutineDispatcher;Ljava/lang/Throwable;)V
+ public fun resumeWith (Ljava/lang/Object;)V
+ public fun toString ()Ljava/lang/String;
+ public fun tryResume (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+ public fun tryResumeWithException (Ljava/lang/Throwable;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/CancellableContinuationKt {
+ public static final fun disposeOnCancellation (Lkotlinx/coroutines/CancellableContinuation;Lkotlinx/coroutines/DisposableHandle;)V
+ public static final fun suspendAtomicCancellableCoroutine (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun suspendAtomicCancellableCoroutine (ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun suspendAtomicCancellableCoroutine$default (ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+ public static final fun suspendCancellableCoroutine (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/coroutines/ChildHandle : kotlinx/coroutines/DisposableHandle {
+ public abstract fun childCancelled (Ljava/lang/Throwable;)Z
+}
+
+public abstract interface class kotlinx/coroutines/ChildJob : kotlinx/coroutines/Job {
+ public abstract fun parentCancelled (Lkotlinx/coroutines/ParentJob;)V
+}
+
+public final class kotlinx/coroutines/ChildJob$DefaultImpls {
+ public static synthetic fun cancel (Lkotlinx/coroutines/ChildJob;)V
+ public static fun fold (Lkotlinx/coroutines/ChildJob;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/ChildJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/ChildJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/ChildJob;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/ChildJob;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+}
+
+public abstract interface class kotlinx/coroutines/CompletableDeferred : kotlinx/coroutines/Deferred {
+ public abstract fun complete (Ljava/lang/Object;)Z
+ public abstract fun completeExceptionally (Ljava/lang/Throwable;)Z
+}
+
+public final class kotlinx/coroutines/CompletableDeferred$DefaultImpls {
+ public static synthetic fun cancel (Lkotlinx/coroutines/CompletableDeferred;)V
+ public static fun fold (Lkotlinx/coroutines/CompletableDeferred;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/CompletableDeferred;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/CompletableDeferred;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/CompletableDeferred;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/CompletableDeferred;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+}
+
+public final class kotlinx/coroutines/CompletableDeferredKt {
+ public static final fun CompletableDeferred (Ljava/lang/Object;)Lkotlinx/coroutines/CompletableDeferred;
+ public static final fun CompletableDeferred (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/CompletableDeferred;
+ public static synthetic fun CompletableDeferred$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableDeferred;
+}
+
+public abstract interface class kotlinx/coroutines/CompletableJob : kotlinx/coroutines/Job {
+ public abstract fun complete ()Z
+ public abstract fun completeExceptionally (Ljava/lang/Throwable;)Z
+}
+
+public final class kotlinx/coroutines/CompletableJob$DefaultImpls {
+ public static synthetic fun cancel (Lkotlinx/coroutines/CompletableJob;)V
+ public static fun fold (Lkotlinx/coroutines/CompletableJob;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/CompletableJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/CompletableJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/CompletableJob;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/CompletableJob;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+}
+
+public final class kotlinx/coroutines/CompletionHandlerException : java/lang/RuntimeException {
+ public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
+}
+
+public abstract interface class kotlinx/coroutines/CopyableThrowable {
+ public abstract fun createCopy ()Ljava/lang/Throwable;
+}
+
+public final class kotlinx/coroutines/CoroutineContextKt {
+ public static final fun newCoroutineContext (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+}
+
+public abstract class kotlinx/coroutines/CoroutineDispatcher : kotlin/coroutines/AbstractCoroutineContextElement, kotlin/coroutines/ContinuationInterceptor {
+ public fun <init> ()V
+ public abstract fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun dispatchYield (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public final fun interceptContinuation (Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+ 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 fun toString ()Ljava/lang/String;
+}
+
+public abstract interface class kotlinx/coroutines/CoroutineExceptionHandler : kotlin/coroutines/CoroutineContext$Element {
+ public static final field Key Lkotlinx/coroutines/CoroutineExceptionHandler$Key;
+ public abstract fun handleException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V
+}
+
+public final class kotlinx/coroutines/CoroutineExceptionHandler$DefaultImpls {
+ public static fun fold (Lkotlinx/coroutines/CoroutineExceptionHandler;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/CoroutineExceptionHandler;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/CoroutineExceptionHandler;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/CoroutineExceptionHandler;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+}
+
+public final class kotlinx/coroutines/CoroutineExceptionHandler$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
+public final class kotlinx/coroutines/CoroutineExceptionHandlerKt {
+ public static final fun CoroutineExceptionHandler (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/CoroutineExceptionHandler;
+ public static final fun handleCoroutineException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V
+}
+
+public final class kotlinx/coroutines/CoroutineName : kotlin/coroutines/AbstractCoroutineContextElement {
+ public static final field Key Lkotlinx/coroutines/CoroutineName$Key;
+ public fun <init> (Ljava/lang/String;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;)Lkotlinx/coroutines/CoroutineName;
+ public static synthetic fun copy$default (Lkotlinx/coroutines/CoroutineName;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/CoroutineName;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class kotlinx/coroutines/CoroutineName$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
+public abstract interface class kotlinx/coroutines/CoroutineScope {
+ public abstract fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
+}
+
+public final class kotlinx/coroutines/CoroutineScopeKt {
+ public static final fun CoroutineScope (Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope;
+ public static final fun MainScope ()Lkotlinx/coroutines/CoroutineScope;
+ public static final fun cancel (Lkotlinx/coroutines/CoroutineScope;Ljava/lang/String;Ljava/lang/Throwable;)V
+ public static final fun cancel (Lkotlinx/coroutines/CoroutineScope;Ljava/util/concurrent/CancellationException;)V
+ public static synthetic fun cancel$default (Lkotlinx/coroutines/CoroutineScope;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)V
+ public static synthetic fun cancel$default (Lkotlinx/coroutines/CoroutineScope;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
+ public static final fun coroutineScope (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun ensureActive (Lkotlinx/coroutines/CoroutineScope;)V
+ public static final fun isActive (Lkotlinx/coroutines/CoroutineScope;)Z
+ public static final fun plus (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope;
+}
+
+public final class kotlinx/coroutines/CoroutineStart : java/lang/Enum {
+ public static final field ATOMIC Lkotlinx/coroutines/CoroutineStart;
+ public static final field DEFAULT Lkotlinx/coroutines/CoroutineStart;
+ public static final field LAZY Lkotlinx/coroutines/CoroutineStart;
+ public static final field UNDISPATCHED Lkotlinx/coroutines/CoroutineStart;
+ public final fun invoke (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
+ public final fun invoke (Lkotlin/jvm/functions/Function2;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+ public final fun isLazy ()Z
+ public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/CoroutineStart;
+ public static fun values ()[Lkotlinx/coroutines/CoroutineStart;
+}
+
+public final class kotlinx/coroutines/DebugKt {
+ public static final field DEBUG_PROPERTY_NAME Ljava/lang/String;
+ public static final field DEBUG_PROPERTY_VALUE_AUTO Ljava/lang/String;
+ public static final field DEBUG_PROPERTY_VALUE_OFF Ljava/lang/String;
+ public static final field DEBUG_PROPERTY_VALUE_ON Ljava/lang/String;
+}
+
+public abstract interface class kotlinx/coroutines/Deferred : kotlinx/coroutines/Job {
+ public abstract fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun getCompleted ()Ljava/lang/Object;
+ public abstract fun getCompletionExceptionOrNull ()Ljava/lang/Throwable;
+ public abstract fun getOnAwait ()Lkotlinx/coroutines/selects/SelectClause1;
+}
+
+public final class kotlinx/coroutines/Deferred$DefaultImpls {
+ public static synthetic fun cancel (Lkotlinx/coroutines/Deferred;)V
+ public static fun fold (Lkotlinx/coroutines/Deferred;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/Deferred;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+}
+
+public abstract interface class kotlinx/coroutines/Delay {
+ public abstract fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/DisposableHandle;
+ public abstract fun scheduleResumeAfterDelay (JLkotlinx/coroutines/CancellableContinuation;)V
+}
+
+public final class kotlinx/coroutines/Delay$DefaultImpls {
+ public static fun delay (Lkotlinx/coroutines/Delay;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static fun invokeOnTimeout (Lkotlinx/coroutines/Delay;JLjava/lang/Runnable;)Lkotlinx/coroutines/DisposableHandle;
+}
+
+public final class kotlinx/coroutines/DelayKt {
+ public static final fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/Dispatchers {
+ public static final field INSTANCE Lkotlinx/coroutines/Dispatchers;
+ public static final fun getDefault ()Lkotlinx/coroutines/CoroutineDispatcher;
+ public static final fun getIO ()Lkotlinx/coroutines/CoroutineDispatcher;
+ public static final fun getMain ()Lkotlinx/coroutines/MainCoroutineDispatcher;
+ public static final fun getUnconfined ()Lkotlinx/coroutines/CoroutineDispatcher;
+}
+
+public final class kotlinx/coroutines/DispatchersKt {
+ public static final field IO_PARALLELISM_PROPERTY_NAME Ljava/lang/String;
+}
+
+public abstract interface class kotlinx/coroutines/DisposableHandle {
+ public abstract fun dispose ()V
+}
+
+public final class kotlinx/coroutines/EventLoopKt {
+ public static final fun processNextEventInCurrentThread ()J
+}
+
+public final class kotlinx/coroutines/ExceptionsKt {
+ public static final fun CancellationException (Ljava/lang/String;Ljava/lang/Throwable;)Ljava/util/concurrent/CancellationException;
+}
+
+public abstract class kotlinx/coroutines/ExecutorCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher, java/io/Closeable {
+ public fun <init> ()V
+ public abstract fun close ()V
+ public abstract fun getExecutor ()Ljava/util/concurrent/Executor;
+}
+
+public final class kotlinx/coroutines/ExecutorsKt {
+ public static final fun asExecutor (Lkotlinx/coroutines/CoroutineDispatcher;)Ljava/util/concurrent/Executor;
+ public static final fun from (Ljava/util/concurrent/Executor;)Lkotlinx/coroutines/CoroutineDispatcher;
+ public static final fun from (Ljava/util/concurrent/ExecutorService;)Lkotlinx/coroutines/ExecutorCoroutineDispatcher;
+}
+
+public abstract interface annotation class kotlinx/coroutines/ExperimentalCoroutinesApi : java/lang/annotation/Annotation {
+}
+
+public abstract interface annotation class kotlinx/coroutines/FlowPreview : java/lang/annotation/Annotation {
+}
+
+public final class kotlinx/coroutines/GlobalScope : kotlinx/coroutines/CoroutineScope {
+ public static final field INSTANCE Lkotlinx/coroutines/GlobalScope;
+ public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
+}
+
+public abstract interface annotation class kotlinx/coroutines/InternalCoroutinesApi : java/lang/annotation/Annotation {
+}
+
+public abstract interface class kotlinx/coroutines/Job : kotlin/coroutines/CoroutineContext$Element {
+ public static final field Key Lkotlinx/coroutines/Job$Key;
+ public abstract fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle;
+ 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 getCancellationException ()Ljava/util/concurrent/CancellationException;
+ public abstract fun getChildren ()Lkotlin/sequences/Sequence;
+ public abstract fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0;
+ public abstract fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
+ public abstract fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
+ public abstract fun isActive ()Z
+ public abstract fun isCancelled ()Z
+ public abstract fun isCompleted ()Z
+ public abstract fun join (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun plus (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+ public abstract fun start ()Z
+}
+
+public final class kotlinx/coroutines/Job$DefaultImpls {
+ public static synthetic fun cancel (Lkotlinx/coroutines/Job;)V
+ public static synthetic fun cancel$default (Lkotlinx/coroutines/Job;Ljava/lang/Throwable;ILjava/lang/Object;)Z
+ public static synthetic fun cancel$default (Lkotlinx/coroutines/Job;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
+ public static fun fold (Lkotlinx/coroutines/Job;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/Job;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static synthetic fun invokeOnCompletion$default (Lkotlinx/coroutines/Job;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/DisposableHandle;
+ public static fun minusKey (Lkotlinx/coroutines/Job;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/Job;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/Job;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+}
+
+public final class kotlinx/coroutines/Job$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
+public final class kotlinx/coroutines/JobKt {
+ public static final fun DisposableHandle (Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/DisposableHandle;
+ public static final fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/CompletableJob;
+ public static final synthetic fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+ public static synthetic fun Job$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableJob;
+ public static synthetic fun Job$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
+ public static final synthetic fun cancel (Lkotlin/coroutines/CoroutineContext;)V
+ public static final synthetic fun cancel (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)Z
+ public static final fun cancel (Lkotlin/coroutines/CoroutineContext;Ljava/util/concurrent/CancellationException;)V
+ public static final fun cancel (Lkotlinx/coroutines/Job;Ljava/lang/String;Ljava/lang/Throwable;)V
+ public static synthetic fun cancel$default (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;ILjava/lang/Object;)Z
+ public static synthetic fun cancel$default (Lkotlin/coroutines/CoroutineContext;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
+ public static synthetic fun cancel$default (Lkotlinx/coroutines/Job;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)V
+ public static final fun cancelAndJoin (Lkotlinx/coroutines/Job;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun cancelChildren (Lkotlin/coroutines/CoroutineContext;)V
+ public static final synthetic fun cancelChildren (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V
+ public static final fun cancelChildren (Lkotlin/coroutines/CoroutineContext;Ljava/util/concurrent/CancellationException;)V
+ public static final synthetic fun cancelChildren (Lkotlinx/coroutines/Job;)V
+ public static final synthetic fun cancelChildren (Lkotlinx/coroutines/Job;Ljava/lang/Throwable;)V
+ public static final fun cancelChildren (Lkotlinx/coroutines/Job;Ljava/util/concurrent/CancellationException;)V
+ public static synthetic fun cancelChildren$default (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;ILjava/lang/Object;)V
+ public static synthetic fun cancelChildren$default (Lkotlin/coroutines/CoroutineContext;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
+ public static synthetic fun cancelChildren$default (Lkotlinx/coroutines/Job;Ljava/lang/Throwable;ILjava/lang/Object;)V
+ public static synthetic fun cancelChildren$default (Lkotlinx/coroutines/Job;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
+ public static final fun cancelFutureOnCancellation (Lkotlinx/coroutines/CancellableContinuation;Ljava/util/concurrent/Future;)V
+ public static final fun cancelFutureOnCompletion (Lkotlinx/coroutines/Job;Ljava/util/concurrent/Future;)Lkotlinx/coroutines/DisposableHandle;
+ public static final fun ensureActive (Lkotlin/coroutines/CoroutineContext;)V
+ public static final fun ensureActive (Lkotlinx/coroutines/Job;)V
+ public static final fun isActive (Lkotlin/coroutines/CoroutineContext;)Z
+}
+
+public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlinx/coroutines/Job, kotlinx/coroutines/ParentJob, kotlinx/coroutines/selects/SelectClause0 {
+ public fun <init> (Z)V
+ protected fun afterCompletionInternal (Ljava/lang/Object;I)V
+ public final fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle;
+ public synthetic fun cancel ()V
+ public synthetic fun cancel (Ljava/lang/Throwable;)Z
+ public fun cancel (Ljava/util/concurrent/CancellationException;)V
+ public final fun cancelCoroutine (Ljava/lang/Throwable;)Z
+ public fun cancelInternal (Ljava/lang/Throwable;)Z
+ public fun childCancelled (Ljava/lang/Throwable;)Z
+ 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 getCancellationException ()Ljava/util/concurrent/CancellationException;
+ public fun getChildJobCancellationCause ()Ljava/util/concurrent/CancellationException;
+ public final fun getChildren ()Lkotlin/sequences/Sequence;
+ protected final fun getCompletionCause ()Ljava/lang/Throwable;
+ protected final fun getCompletionCauseHandled ()Z
+ public final fun getCompletionExceptionOrNull ()Ljava/lang/Throwable;
+ public final fun getKey ()Lkotlin/coroutines/CoroutineContext$Key;
+ public final fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0;
+ protected fun handleJobException (Ljava/lang/Throwable;)Z
+ 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
+ public final fun isCancelled ()Z
+ public final fun isCompleted ()Z
+ public final fun isCompletedExceptionally ()Z
+ protected fun isScopedCoroutine ()Z
+ public final fun join (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ protected fun onCancelling (Ljava/lang/Throwable;)V
+ protected fun onCompletionInternal (Ljava/lang/Object;)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;
+ public final fun registerSelectClause0 (Lkotlinx/coroutines/selects/SelectInstance;Lkotlin/jvm/functions/Function1;)V
+ public final fun start ()Z
+ protected final fun toCancellationException (Ljava/lang/Throwable;Ljava/lang/String;)Ljava/util/concurrent/CancellationException;
+ public static synthetic fun toCancellationException$default (Lkotlinx/coroutines/JobSupport;Ljava/lang/Throwable;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/concurrent/CancellationException;
+ public final fun toDebugString ()Ljava/lang/String;
+ public fun toString ()Ljava/lang/String;
+}
+
+public abstract class kotlinx/coroutines/MainCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher {
+ public fun <init> ()V
+ public abstract fun getImmediate ()Lkotlinx/coroutines/MainCoroutineDispatcher;
+}
+
+public final class kotlinx/coroutines/NonCancellable : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/Job {
+ public static final field INSTANCE Lkotlinx/coroutines/NonCancellable;
+ public fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle;
+ public synthetic fun cancel ()V
+ public synthetic fun cancel (Ljava/lang/Throwable;)Z
+ public fun cancel (Ljava/util/concurrent/CancellationException;)V
+ public fun getCancellationException ()Ljava/util/concurrent/CancellationException;
+ public fun getChildren ()Lkotlin/sequences/Sequence;
+ public fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0;
+ public fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
+ public fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
+ public fun isActive ()Z
+ public fun isCancelled ()Z
+ public fun isCompleted ()Z
+ public fun join (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun plus (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+ public fun start ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class kotlinx/coroutines/NonDisposableHandle : kotlinx/coroutines/ChildHandle, kotlinx/coroutines/DisposableHandle {
+ public static final field INSTANCE Lkotlinx/coroutines/NonDisposableHandle;
+ public fun childCancelled (Ljava/lang/Throwable;)Z
+ public fun dispose ()V
+ public fun toString ()Ljava/lang/String;
+}
+
+public abstract interface annotation class kotlinx/coroutines/ObsoleteCoroutinesApi : java/lang/annotation/Annotation {
+}
+
+public abstract interface class kotlinx/coroutines/ParentJob : kotlinx/coroutines/Job {
+ public abstract fun getChildJobCancellationCause ()Ljava/util/concurrent/CancellationException;
+}
+
+public final class kotlinx/coroutines/ParentJob$DefaultImpls {
+ public static synthetic fun cancel (Lkotlinx/coroutines/ParentJob;)V
+ public static fun fold (Lkotlinx/coroutines/ParentJob;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/ParentJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/ParentJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/ParentJob;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/ParentJob;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+}
+
+public final class kotlinx/coroutines/RunnableKt {
+ public static final fun Runnable (Lkotlin/jvm/functions/Function0;)Ljava/lang/Runnable;
+}
+
+public final class kotlinx/coroutines/SupervisorKt {
+ public static final fun SupervisorJob (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/CompletableJob;
+ public static final synthetic fun SupervisorJob (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
+ public static synthetic fun SupervisorJob$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableJob;
+ public static synthetic fun SupervisorJob$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
+ public static final fun supervisorScope (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/coroutines/ThreadContextElement : kotlin/coroutines/CoroutineContext$Element {
+ public abstract fun restoreThreadContext (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Object;)V
+ public abstract fun updateThreadContext (Lkotlin/coroutines/CoroutineContext;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/ThreadContextElement$DefaultImpls {
+ public static fun fold (Lkotlinx/coroutines/ThreadContextElement;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/ThreadContextElement;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/ThreadContextElement;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/ThreadContextElement;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+}
+
+public final class kotlinx/coroutines/ThreadContextElementKt {
+ public static final fun asContextElement (Ljava/lang/ThreadLocal;Ljava/lang/Object;)Lkotlinx/coroutines/ThreadContextElement;
+ public static synthetic fun asContextElement$default (Ljava/lang/ThreadLocal;Ljava/lang/Object;ILjava/lang/Object;)Lkotlinx/coroutines/ThreadContextElement;
+ public static final fun ensurePresent (Ljava/lang/ThreadLocal;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun isPresent (Ljava/lang/ThreadLocal;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/ThreadPoolDispatcherKt {
+ public static final fun newFixedThreadPoolContext (ILjava/lang/String;)Lkotlinx/coroutines/ExecutorCoroutineDispatcher;
+ public static final fun newSingleThreadContext (Ljava/lang/String;)Lkotlinx/coroutines/ExecutorCoroutineDispatcher;
+}
+
+public final class kotlinx/coroutines/TimeoutCancellationException : java/util/concurrent/CancellationException {
+}
+
+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 withTimeoutOrNull (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/YieldKt {
+ public static final fun yield (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/channels/ActorKt {
+ public static final fun actor (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/SendChannel;
+ public static synthetic fun actor$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/SendChannel;
+}
+
+public abstract interface class kotlinx/coroutines/channels/ActorScope : kotlinx/coroutines/CoroutineScope, kotlinx/coroutines/channels/ReceiveChannel {
+ public abstract fun getChannel ()Lkotlinx/coroutines/channels/Channel;
+}
+
+public final class kotlinx/coroutines/channels/ActorScope$DefaultImpls {
+ public static synthetic fun cancel (Lkotlinx/coroutines/channels/ActorScope;)V
+}
+
+public abstract interface class kotlinx/coroutines/channels/BroadcastChannel : kotlinx/coroutines/channels/SendChannel {
+ public abstract synthetic fun cancel (Ljava/lang/Throwable;)Z
+ public abstract fun cancel (Ljava/util/concurrent/CancellationException;)V
+ public abstract fun openSubscription ()Lkotlinx/coroutines/channels/ReceiveChannel;
+}
+
+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 final class kotlinx/coroutines/channels/BroadcastChannelKt {
+ public static final fun BroadcastChannel (I)Lkotlinx/coroutines/channels/BroadcastChannel;
+}
+
+public final class kotlinx/coroutines/channels/BroadcastKt {
+ public static final fun broadcast (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/BroadcastChannel;
+ public static final fun broadcast (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlinx/coroutines/CoroutineStart;)Lkotlinx/coroutines/channels/BroadcastChannel;
+ public static synthetic fun broadcast$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/BroadcastChannel;
+ public static synthetic fun broadcast$default (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlinx/coroutines/CoroutineStart;ILjava/lang/Object;)Lkotlinx/coroutines/channels/BroadcastChannel;
+}
+
+public abstract interface class kotlinx/coroutines/channels/Channel : kotlinx/coroutines/channels/ReceiveChannel, kotlinx/coroutines/channels/SendChannel {
+ public static final field BUFFERED I
+ public static final field CONFLATED I
+ public static final field DEFAULT_BUFFER_PROPERTY_NAME Ljava/lang/String;
+ public static final field Factory Lkotlinx/coroutines/channels/Channel$Factory;
+ public static final field RENDEZVOUS I
+ public static final field UNLIMITED I
+}
+
+public final class kotlinx/coroutines/channels/Channel$DefaultImpls {
+ public static synthetic fun cancel (Lkotlinx/coroutines/channels/Channel;)V
+}
+
+public final class kotlinx/coroutines/channels/Channel$Factory {
+ public static final field BUFFERED I
+ public static final field CONFLATED I
+ public static final field DEFAULT_BUFFER_PROPERTY_NAME Ljava/lang/String;
+ public static final field RENDEZVOUS I
+ public static final field UNLIMITED I
+}
+
+public abstract interface class kotlinx/coroutines/channels/ChannelIterator {
+ public abstract fun hasNext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun next ()Ljava/lang/Object;
+ public abstract synthetic fun next (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/channels/ChannelIterator$DefaultImpls {
+ public static synthetic fun next (Lkotlinx/coroutines/channels/ChannelIterator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/channels/ChannelKt {
+ public static final fun Channel (I)Lkotlinx/coroutines/channels/Channel;
+ public static synthetic fun Channel$default (IILjava/lang/Object;)Lkotlinx/coroutines/channels/Channel;
+}
+
+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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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;
+}
+
+public final class kotlinx/coroutines/channels/ClosedReceiveChannelException : java/util/NoSuchElementException {
+ public fun <init> (Ljava/lang/String;)V
+}
+
+public final class kotlinx/coroutines/channels/ClosedSendChannelException : java/lang/IllegalStateException {
+ public fun <init> (Ljava/lang/String;)V
+}
+
+public final class kotlinx/coroutines/channels/ConflatedBroadcastChannel : kotlinx/coroutines/channels/BroadcastChannel {
+ public fun <init> ()V
+ public fun <init> (Ljava/lang/Object;)V
+ public synthetic fun cancel (Ljava/lang/Throwable;)Z
+ public fun cancel (Ljava/util/concurrent/CancellationException;)V
+ public fun close (Ljava/lang/Throwable;)Z
+ public fun getOnSend ()Lkotlinx/coroutines/selects/SelectClause2;
+ public final fun getValue ()Ljava/lang/Object;
+ 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 final class kotlinx/coroutines/channels/ProduceKt {
+ public static final fun awaitClose (Lkotlinx/coroutines/channels/ProducerScope;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun awaitClose$default (Lkotlinx/coroutines/channels/ProducerScope;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+ public static final fun produce (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final fun produce (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static synthetic fun produce$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static synthetic fun produce$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
+}
+
+public abstract interface class kotlinx/coroutines/channels/ProducerScope : kotlinx/coroutines/CoroutineScope, kotlinx/coroutines/channels/SendChannel {
+ public abstract fun getChannel ()Lkotlinx/coroutines/channels/SendChannel;
+}
+
+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 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 (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun receiveOrNull (Lkotlin/coroutines/Continuation;)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 abstract interface class kotlinx/coroutines/channels/SendChannel {
+ public abstract fun close (Ljava/lang/Throwable;)Z
+ 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 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 final class kotlinx/coroutines/channels/TickerChannelsKt {
+ public static final fun ticker (JJLkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/channels/TickerMode;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static synthetic fun ticker$default (JJLkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/channels/TickerMode;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
+}
+
+public final class kotlinx/coroutines/channels/TickerMode : java/lang/Enum {
+ public static final field FIXED_DELAY Lkotlinx/coroutines/channels/TickerMode;
+ public static final field FIXED_PERIOD Lkotlinx/coroutines/channels/TickerMode;
+ public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/channels/TickerMode;
+ 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 abstract class kotlinx/coroutines/flow/AbstractFlow : kotlinx/coroutines/flow/Flow {
+ public fun <init> ()V
+ public final fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun collectSafely (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/coroutines/flow/Flow {
+ public abstract fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/coroutines/flow/FlowCollector {
+ public abstract fun emit (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/flow/FlowKt {
+ public static final field DEFAULT_CONCURRENCY_PROPERTY_NAME Ljava/lang/String;
+ public static final fun asFlow (Ljava/lang/Iterable;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow (Ljava/util/Iterator;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow (Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow (Lkotlin/ranges/IntRange;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow (Lkotlin/ranges/LongRange;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow (Lkotlin/sequences/Sequence;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow (Lkotlinx/coroutines/channels/BroadcastChannel;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow ([I)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow ([J)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asFlow ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun broadcastIn (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;)Lkotlinx/coroutines/channels/BroadcastChannel;
+ public static synthetic fun broadcastIn$default (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;ILjava/lang/Object;)Lkotlinx/coroutines/channels/BroadcastChannel;
+ public static final fun buffer (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
+ public static synthetic fun buffer$default (Lkotlinx/coroutines/flow/Flow;IILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun callbackFlow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun catch (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun channelFlow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun collect (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun collect (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun collectIndexed (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun collectLatest (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun combine (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combine (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combine (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combine (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function5;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combine (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function6;)Lkotlinx/coroutines/flow/Flow;
+ public static final synthetic fun combine ([Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function5;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function6;)Lkotlinx/coroutines/flow/Flow;
+ public static final synthetic fun combineTransform (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combineTransform (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combineTransform (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function5;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combineTransform (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function6;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun combineTransform (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function7;)Lkotlinx/coroutines/flow/Flow;
+ public static final synthetic fun combineTransform ([Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun compose (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun concatMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun concatWith (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun concatWith (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun conflate (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun consumeAsFlow (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun count (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ 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 delayEach (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
+ public static final fun delayFlow (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
+ public static final fun distinctUntilChanged (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun distinctUntilChanged (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun distinctUntilChangedBy (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun drop (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
+ public static final fun dropWhile (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun emitAll (Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun emitAll (Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun emptyFlow ()Lkotlinx/coroutines/flow/Flow;
+ public static final fun filter (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final synthetic fun filterIsInstance (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun filterNot (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun filterNotNull (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun first (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun first (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun flatMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flatMapConcat (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flatMapLatest (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flatMapMerge (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static synthetic fun flatMapMerge$default (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flatten (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flattenConcat (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flattenMerge (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
+ public static synthetic fun flattenMerge$default (Lkotlinx/coroutines/flow/Flow;IILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flowCombine (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flowCombineTransform (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flowOf (Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flowOf ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flowOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flowViaChannel (ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static synthetic fun flowViaChannel$default (ILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flowWith (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static synthetic fun flowWith$default (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ 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 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;
+ public static final fun mapNotNull (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun merge (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun observeOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
+ public static final synthetic fun onCompletion (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onCompletion (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onErrorCollect (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static synthetic fun onErrorCollect$default (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onErrorResume (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onErrorResumeNext (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onErrorReturn (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onErrorReturn (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static synthetic fun onErrorReturn$default (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onStart (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun produceIn (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final fun publishOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun reduce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun retry (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun retry (Lkotlinx/coroutines/flow/Flow;JLkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ 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 sample (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;
+ public static final fun single (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun singleOrNull (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun skip (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
+ public static final fun startWith (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun startWith (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;)V
+ public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V
+ public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V
+ public static final fun subscribeOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun switchMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun take (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
+ public static final fun takeWhile (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun toCollection (Lkotlinx/coroutines/flow/Flow;Ljava/util/Collection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun toList (Lkotlinx/coroutines/flow/Flow;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun toList$default (Lkotlinx/coroutines/flow/Flow;Ljava/util/List;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+ public static final fun toSet (Lkotlinx/coroutines/flow/Flow;Ljava/util/Set;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun toSet$default (Lkotlinx/coroutines/flow/Flow;Ljava/util/Set;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+ public static final fun transform (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun transformLatest (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun unsafeTransform (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun withContext (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;)V
+ public static final fun withIndex (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun zip (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+}
+
+public abstract class kotlinx/coroutines/flow/internal/ChannelFlow : kotlinx/coroutines/flow/Flow {
+ public final field capacity I
+ public final field context Lkotlin/coroutines/CoroutineContext;
+ public fun <init> (Lkotlin/coroutines/CoroutineContext;I)V
+ public 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;I)Lkotlinx/coroutines/flow/internal/ChannelFlow;
+ public fun produceImpl (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public fun toString ()Ljava/lang/String;
+ public final fun update (Lkotlin/coroutines/CoroutineContext;I)Lkotlinx/coroutines/flow/internal/ChannelFlow;
+ public static synthetic fun update$default (Lkotlinx/coroutines/flow/internal/ChannelFlow;Lkotlin/coroutines/CoroutineContext;IILjava/lang/Object;)Lkotlinx/coroutines/flow/internal/ChannelFlow;
+}
+
+public final class kotlinx/coroutines/flow/internal/CombineKt {
+ public static final fun combineInternal (Lkotlinx/coroutines/flow/FlowCollector;[Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/flow/internal/FlowExceptions_commonKt {
+ public static final fun checkIndexOverflow (I)I
+}
+
+public final class kotlinx/coroutines/flow/internal/SafeCollectorKt {
+ public static final fun unsafeFlow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+}
+
+public final class kotlinx/coroutines/flow/internal/SendingCollector : kotlinx/coroutines/flow/FlowCollector {
+ public fun <init> (Lkotlinx/coroutines/channels/SendChannel;)V
+ public fun emit (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/intrinsics/CancellableKt {
+ public static final fun startCoroutineCancellable (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
+}
+
+public class kotlinx/coroutines/scheduling/ExperimentalCoroutineDispatcher : kotlinx/coroutines/ExecutorCoroutineDispatcher {
+ public synthetic fun <init> (II)V
+ public synthetic fun <init> (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun <init> (IIJLjava/lang/String;)V
+ public synthetic fun <init> (IIJLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun <init> (IILjava/lang/String;)V
+ public synthetic fun <init> (IILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun blocking (I)Lkotlinx/coroutines/CoroutineDispatcher;
+ public static synthetic fun blocking$default (Lkotlinx/coroutines/scheduling/ExperimentalCoroutineDispatcher;IILjava/lang/Object;)Lkotlinx/coroutines/CoroutineDispatcher;
+ public fun close ()V
+ public fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun dispatchYield (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun getExecutor ()Ljava/util/concurrent/Executor;
+ public final fun limited (I)Lkotlinx/coroutines/CoroutineDispatcher;
+ public fun toString ()Ljava/lang/String;
+}
+
+public abstract interface class kotlinx/coroutines/selects/SelectBuilder {
+ public abstract fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V
+ public abstract fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V
+ public abstract fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+ public abstract fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V
+ public abstract fun onTimeout (JLkotlin/jvm/functions/Function1;)V
+}
+
+public final class kotlinx/coroutines/selects/SelectBuilder$DefaultImpls {
+ public static fun invoke (Lkotlinx/coroutines/selects/SelectBuilder;Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V
+}
+
+public final class kotlinx/coroutines/selects/SelectBuilderImpl : kotlinx/coroutines/internal/LockFreeLinkedListHead, kotlin/coroutines/Continuation, kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/selects/SelectBuilder, kotlinx/coroutines/selects/SelectInstance {
+ public fun <init> (Lkotlin/coroutines/Continuation;)V
+ public fun disposeOnSelect (Lkotlinx/coroutines/DisposableHandle;)V
+ public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
+ public fun getCompletion ()Lkotlin/coroutines/Continuation;
+ public fun getContext ()Lkotlin/coroutines/CoroutineContext;
+ public final fun getResult ()Ljava/lang/Object;
+ public fun getStackTraceElement ()Ljava/lang/StackTraceElement;
+ public final fun handleBuilderException (Ljava/lang/Throwable;)V
+ public fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V
+ public fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V
+ public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+ public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V
+ public fun isSelected ()Z
+ public fun onTimeout (JLkotlin/jvm/functions/Function1;)V
+ public fun performAtomicTrySelect (Lkotlinx/coroutines/internal/AtomicDesc;)Ljava/lang/Object;
+ public fun resumeSelectCancellableWithException (Ljava/lang/Throwable;)V
+ public fun resumeWith (Ljava/lang/Object;)V
+ public fun trySelect (Ljava/lang/Object;)Z
+}
+
+public abstract interface class kotlinx/coroutines/selects/SelectClause0 {
+ public abstract fun registerSelectClause0 (Lkotlinx/coroutines/selects/SelectInstance;Lkotlin/jvm/functions/Function1;)V
+}
+
+public abstract interface class kotlinx/coroutines/selects/SelectClause1 {
+ public abstract fun registerSelectClause1 (Lkotlinx/coroutines/selects/SelectInstance;Lkotlin/jvm/functions/Function2;)V
+}
+
+public abstract interface class kotlinx/coroutines/selects/SelectClause2 {
+ public abstract fun registerSelectClause2 (Lkotlinx/coroutines/selects/SelectInstance;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+}
+
+public abstract interface class kotlinx/coroutines/selects/SelectInstance {
+ public abstract fun disposeOnSelect (Lkotlinx/coroutines/DisposableHandle;)V
+ public abstract fun getCompletion ()Lkotlin/coroutines/Continuation;
+ public abstract fun isSelected ()Z
+ public abstract fun performAtomicTrySelect (Lkotlinx/coroutines/internal/AtomicDesc;)Ljava/lang/Object;
+ public abstract fun resumeSelectCancellableWithException (Ljava/lang/Throwable;)V
+ public abstract fun trySelect (Ljava/lang/Object;)Z
+}
+
+public final class kotlinx/coroutines/selects/SelectKt {
+ public static final fun select (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/selects/SelectUnbiasedKt {
+ public static final fun selectUnbiased (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/selects/UnbiasedSelectBuilderImpl : kotlinx/coroutines/selects/SelectBuilder {
+ public fun <init> (Lkotlin/coroutines/Continuation;)V
+ public final fun getClauses ()Ljava/util/ArrayList;
+ public final fun getInstance ()Lkotlinx/coroutines/selects/SelectBuilderImpl;
+ public final fun handleBuilderException (Ljava/lang/Throwable;)V
+ public final fun initSelectResult ()Ljava/lang/Object;
+ public fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V
+ public fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V
+ public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+ public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V
+ public fun onTimeout (JLkotlin/jvm/functions/Function1;)V
+}
+
+public final class kotlinx/coroutines/selects/WhileSelectKt {
+ public static final fun whileSelect (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/coroutines/sync/Mutex {
+ public abstract fun getOnLock ()Lkotlinx/coroutines/selects/SelectClause2;
+ public abstract fun holdsLock (Ljava/lang/Object;)Z
+ public abstract fun isLocked ()Z
+ public abstract fun lock (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun tryLock (Ljava/lang/Object;)Z
+ public abstract fun unlock (Ljava/lang/Object;)V
+}
+
+public final class kotlinx/coroutines/sync/Mutex$DefaultImpls {
+ public static synthetic fun lock$default (Lkotlinx/coroutines/sync/Mutex;Ljava/lang/Object;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+ public static synthetic fun tryLock$default (Lkotlinx/coroutines/sync/Mutex;Ljava/lang/Object;ILjava/lang/Object;)Z
+ public static synthetic fun unlock$default (Lkotlinx/coroutines/sync/Mutex;Ljava/lang/Object;ILjava/lang/Object;)V
+}
+
+public final class kotlinx/coroutines/sync/MutexKt {
+ public static final fun Mutex (Z)Lkotlinx/coroutines/sync/Mutex;
+ public static synthetic fun Mutex$default (ZILjava/lang/Object;)Lkotlinx/coroutines/sync/Mutex;
+ public static final fun withLock (Lkotlinx/coroutines/sync/Mutex;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun withLock$default (Lkotlinx/coroutines/sync/Mutex;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/coroutines/sync/Semaphore {
+ public abstract fun acquire (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun getAvailablePermits ()I
+ public abstract fun release ()V
+ public abstract fun tryAcquire ()Z
+}
+
+public final class kotlinx/coroutines/sync/SemaphoreKt {
+ public static final fun Semaphore (II)Lkotlinx/coroutines/sync/Semaphore;
+ public static synthetic fun Semaphore$default (IIILjava/lang/Object;)Lkotlinx/coroutines/sync/Semaphore;
+ public static final fun withPermit (Lkotlinx/coroutines/sync/Semaphore;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/test/TestCoroutineContext : kotlin/coroutines/CoroutineContext {
+ public fun <init> ()V
+ public fun <init> (Ljava/lang/String;)V
+ public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun advanceTimeBy (JLjava/util/concurrent/TimeUnit;)J
+ public static synthetic fun advanceTimeBy$default (Lkotlinx/coroutines/test/TestCoroutineContext;JLjava/util/concurrent/TimeUnit;ILjava/lang/Object;)J
+ public final fun advanceTimeTo (JLjava/util/concurrent/TimeUnit;)V
+ public static synthetic fun advanceTimeTo$default (Lkotlinx/coroutines/test/TestCoroutineContext;JLjava/util/concurrent/TimeUnit;ILjava/lang/Object;)V
+ public final fun assertAllUnhandledExceptions (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun assertAllUnhandledExceptions$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public final fun assertAnyUnhandledException (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun assertAnyUnhandledException$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public final fun assertExceptions (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun assertExceptions$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public final fun assertUnhandledException (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun assertUnhandledException$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public final fun cancelAllActions ()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 getExceptions ()Ljava/util/List;
+ public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public final fun now (Ljava/util/concurrent/TimeUnit;)J
+ public static synthetic fun now$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/util/concurrent/TimeUnit;ILjava/lang/Object;)J
+ public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+ public fun toString ()Ljava/lang/String;
+ public final fun triggerActions ()V
+}
+
+public final class kotlinx/coroutines/test/TestCoroutineContextKt {
+ public static final fun withTestContext (Lkotlinx/coroutines/test/TestCoroutineContext;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun withTestContext$default (Lkotlinx/coroutines/test/TestCoroutineContext;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt
new file mode 100644
index 00000000..79f5b75d
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt
@@ -0,0 +1,50 @@
+public final class kotlinx/coroutines/debug/CoroutineInfo {
+ public final fun copy ()Lkotlinx/coroutines/debug/CoroutineInfo;
+ public final fun getContext ()Lkotlin/coroutines/CoroutineContext;
+ public final fun getCreationStackTrace ()Ljava/util/List;
+ public final fun getJob ()Lkotlinx/coroutines/Job;
+ public final fun getState ()Lkotlinx/coroutines/debug/State;
+ public final fun lastObservedStackTrace ()Ljava/util/List;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class kotlinx/coroutines/debug/DebugProbes {
+ public static final field INSTANCE Lkotlinx/coroutines/debug/DebugProbes;
+ public final fun dumpCoroutines (Ljava/io/PrintStream;)V
+ public static synthetic fun dumpCoroutines$default (Lkotlinx/coroutines/debug/DebugProbes;Ljava/io/PrintStream;ILjava/lang/Object;)V
+ public final fun dumpCoroutinesInfo ()Ljava/util/List;
+ public final fun getSanitizeStackTraces ()Z
+ public final fun install ()V
+ public final fun jobToString (Lkotlinx/coroutines/Job;)Ljava/lang/String;
+ public final fun printJob (Lkotlinx/coroutines/Job;Ljava/io/PrintStream;)V
+ public static synthetic fun printJob$default (Lkotlinx/coroutines/debug/DebugProbes;Lkotlinx/coroutines/Job;Ljava/io/PrintStream;ILjava/lang/Object;)V
+ public final fun printScope (Lkotlinx/coroutines/CoroutineScope;Ljava/io/PrintStream;)V
+ public static synthetic fun printScope$default (Lkotlinx/coroutines/debug/DebugProbes;Lkotlinx/coroutines/CoroutineScope;Ljava/io/PrintStream;ILjava/lang/Object;)V
+ public final fun scopeToString (Lkotlinx/coroutines/CoroutineScope;)Ljava/lang/String;
+ public final fun setSanitizeStackTraces (Z)V
+ public final fun uninstall ()V
+ public final fun withDebugProbes (Lkotlin/jvm/functions/Function0;)V
+}
+
+public final class kotlinx/coroutines/debug/State : java/lang/Enum {
+ public static final field CREATED Lkotlinx/coroutines/debug/State;
+ public static final field RUNNING Lkotlinx/coroutines/debug/State;
+ public static final field SUSPENDED Lkotlinx/coroutines/debug/State;
+ public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/debug/State;
+ public static fun values ()[Lkotlinx/coroutines/debug/State;
+}
+
+public final class kotlinx/coroutines/debug/junit4/CoroutinesTimeout : org/junit/rules/TestRule {
+ public static final field Companion Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion;
+ public fun <init> (JZ)V
+ public synthetic fun <init> (JZILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun apply (Lorg/junit/runners/model/Statement;Lorg/junit/runner/Description;)Lorg/junit/runners/model/Statement;
+}
+
+public final class kotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion {
+ public final fun seconds (IZ)Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout;
+ public final fun seconds (JZ)Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout;
+ public static synthetic fun seconds$default (Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion;IZILjava/lang/Object;)Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout;
+ public static synthetic fun seconds$default (Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion;JZILjava/lang/Object;)Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-guava.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-guava.txt
new file mode 100644
index 00000000..26bc229e
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-guava.txt
@@ -0,0 +1,8 @@
+public final class kotlinx/coroutines/guava/ListenableFutureKt {
+ public static final fun asDeferred (Lcom/google/common/util/concurrent/ListenableFuture;)Lkotlinx/coroutines/Deferred;
+ public static final fun asListenableFuture (Lkotlinx/coroutines/Deferred;)Lcom/google/common/util/concurrent/ListenableFuture;
+ public static final fun await (Lcom/google/common/util/concurrent/ListenableFuture;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lcom/google/common/util/concurrent/ListenableFuture;
+ public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/google/common/util/concurrent/ListenableFuture;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-javafx.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-javafx.txt
new file mode 100644
index 00000000..24c5b70b
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-javafx.txt
@@ -0,0 +1,12 @@
+public abstract class kotlinx/coroutines/javafx/JavaFxDispatcher : kotlinx/coroutines/MainCoroutineDispatcher, kotlinx/coroutines/Delay {
+ public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/DisposableHandle;
+ public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/CancellableContinuation;)V
+}
+
+public final class kotlinx/coroutines/javafx/JavaFxDispatcherKt {
+ public static final fun awaitPulse (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun getJavaFx (Lkotlinx/coroutines/Dispatchers;)Lkotlinx/coroutines/javafx/JavaFxDispatcher;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-jdk8.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-jdk8.txt
new file mode 100644
index 00000000..7067e847
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-jdk8.txt
@@ -0,0 +1,16 @@
+public final class kotlinx/coroutines/future/FutureKt {
+ public static final fun asCompletableFuture (Lkotlinx/coroutines/Deferred;)Ljava/util/concurrent/CompletableFuture;
+ public static final fun asCompletableFuture (Lkotlinx/coroutines/Job;)Ljava/util/concurrent/CompletableFuture;
+ public static final fun asDeferred (Ljava/util/concurrent/CompletionStage;)Lkotlinx/coroutines/Deferred;
+ public static final fun await (Ljava/util/concurrent/CompletionStage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture;
+ public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture;
+}
+
+public final class kotlinx/coroutines/time/TimeKt {
+ public static final fun delay (Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;Ljava/time/Duration;Lkotlin/jvm/functions/Function1;)V
+ public static final fun withTimeout (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun withTimeoutOrNull (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-play-services.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-play-services.txt
new file mode 100644
index 00000000..9b2c4dd3
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-play-services.txt
@@ -0,0 +1,6 @@
+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 asTask (Lkotlinx/coroutines/Deferred;)Lcom/google/android/gms/tasks/Task;
+ public static final fun await (Lcom/google/android/gms/tasks/Task;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt
new file mode 100644
index 00000000..60baa743
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt
@@ -0,0 +1,69 @@
+public final class kotlinx/coroutines/reactive/AwaitKt {
+ public static final fun awaitFirst (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirstOrDefault (Lorg/reactivestreams/Publisher;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirstOrElse (Lorg/reactivestreams/Publisher;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirstOrNull (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitLast (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitSingle (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+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 abstract interface class kotlinx/coroutines/reactive/ContextInjector {
+ public abstract fun injectCoroutineContext (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/CoroutineContext;)Lorg/reactivestreams/Publisher;
+}
+
+public final class kotlinx/coroutines/reactive/ConvertKt {
+ public static final fun asPublisher (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lorg/reactivestreams/Publisher;
+ public static synthetic fun asPublisher$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lorg/reactivestreams/Publisher;
+}
+
+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 final class kotlinx/coroutines/reactive/FlowSubscription : kotlinx/coroutines/AbstractCoroutine, org/reactivestreams/Subscription {
+ public final field flow Lkotlinx/coroutines/flow/Flow;
+ public final field subscriber Lorg/reactivestreams/Subscriber;
+ public fun <init> (Lkotlinx/coroutines/flow/Flow;Lorg/reactivestreams/Subscriber;)V
+ public fun cancel ()V
+ public fun request (J)V
+}
+
+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 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;)Lorg/reactivestreams/Publisher;
+}
+
+public final class kotlinx/coroutines/reactive/PublisherCoroutine : kotlinx/coroutines/AbstractCoroutine, kotlinx/coroutines/channels/ProducerScope, kotlinx/coroutines/selects/SelectClause2, org/reactivestreams/Subscription {
+ public fun <init> (Lkotlin/coroutines/CoroutineContext;Lorg/reactivestreams/Subscriber;)V
+ public fun cancel ()V
+ public fun close (Ljava/lang/Throwable;)Z
+ public fun getChannel ()Lkotlinx/coroutines/channels/SendChannel;
+ public fun getOnSend ()Lkotlinx/coroutines/selects/SelectClause2;
+ 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 final class kotlinx/coroutines/reactive/ReactiveFlowKt {
+ public static final fun asFlow (Lorg/reactivestreams/Publisher;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun asPublisher (Lkotlinx/coroutines/flow/Flow;)Lorg/reactivestreams/Publisher;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt
new file mode 100644
index 00000000..422f36b1
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt
@@ -0,0 +1,58 @@
+public final class kotlinx/coroutines/reactor/ConvertKt {
+ public static final fun asFlux (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lreactor/core/publisher/Flux;
+ public static synthetic fun asFlux$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lreactor/core/publisher/Flux;
+ public static final fun asMono (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext;)Lreactor/core/publisher/Mono;
+ public static final fun asMono (Lkotlinx/coroutines/Job;Lkotlin/coroutines/CoroutineContext;)Lreactor/core/publisher/Mono;
+}
+
+public final class kotlinx/coroutines/reactor/FlowKt {
+ public static final 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 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 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 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;
+}
+
+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 final fun getContext ()Lreactor/util/context/Context;
+}
+
+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 final class kotlinx/coroutines/reactor/ReactorFlowKt {
+ public static final fun asFlux (Lkotlinx/coroutines/flow/Flow;)Lreactor/core/publisher/Flux;
+}
+
+public final class kotlinx/coroutines/reactor/SchedulerCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher, kotlinx/coroutines/Delay {
+ public fun <init> (Lreactor/core/scheduler/Scheduler;)V
+ public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getScheduler ()Lreactor/core/scheduler/Scheduler;
+ public fun hashCode ()I
+ public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/DisposableHandle;
+ public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/CancellableContinuation;)V
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class kotlinx/coroutines/reactor/SchedulerKt {
+ public static final fun asCoroutineDispatcher (Lreactor/core/scheduler/Scheduler;)Lkotlinx/coroutines/reactor/SchedulerCoroutineDispatcher;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt
new file mode 100644
index 00000000..54a9663a
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt
@@ -0,0 +1,82 @@
+public final class kotlinx/coroutines/rx2/RxAwaitKt {
+ public static final fun await (Lio/reactivex/CompletableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun await (Lio/reactivex/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun await (Lio/reactivex/SingleSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirst (Lio/reactivex/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirstOrDefault (Lio/reactivex/ObservableSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirstOrElse (Lio/reactivex/ObservableSource;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ 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/ObservableSource;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 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 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;
+}
+
+public final class kotlinx/coroutines/rx2/RxConvertKt {
+ public static final fun asCompletable (Lkotlinx/coroutines/Job;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Completable;
+ public static final fun asMaybe (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Maybe;
+ public static final fun asObservable (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Observable;
+ public static final fun asSingle (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Single;
+ public static final fun from (Lkotlinx/coroutines/flow/Flow;)Lio/reactivex/Flowable;
+ public static final fun from (Lkotlinx/coroutines/flow/Flow;)Lio/reactivex/Observable;
+}
+
+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 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 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 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;
+}
+
+public final class kotlinx/coroutines/rx2/RxSchedulerKt {
+ public static final fun asCoroutineDispatcher (Lio/reactivex/Scheduler;)Lkotlinx/coroutines/rx2/SchedulerCoroutineDispatcher;
+}
+
+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 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;
+}
+
+public final class kotlinx/coroutines/rx2/SchedulerCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher, kotlinx/coroutines/Delay {
+ public fun <init> (Lio/reactivex/Scheduler;)V
+ public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getScheduler ()Lio/reactivex/Scheduler;
+ public fun hashCode ()I
+ public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/DisposableHandle;
+ public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/CancellableContinuation;)V
+ public fun toString ()Ljava/lang/String;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-slf4j.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-slf4j.txt
new file mode 100644
index 00000000..a8bf271b
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-slf4j.txt
@@ -0,0 +1,19 @@
+public final class kotlinx/coroutines/slf4j/MDCContext : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/ThreadContextElement {
+ public static final field Key Lkotlinx/coroutines/slf4j/MDCContext$Key;
+ 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;
+ public fun updateThreadContext (Lkotlin/coroutines/CoroutineContext;)Ljava/util/Map;
+}
+
+public final class kotlinx/coroutines/slf4j/MDCContext$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-swing.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-swing.txt
new file mode 100644
index 00000000..09556e80
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-swing.txt
@@ -0,0 +1,11 @@
+public abstract class kotlinx/coroutines/swing/SwingDispatcher : kotlinx/coroutines/MainCoroutineDispatcher, kotlinx/coroutines/Delay {
+ public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/DisposableHandle;
+ public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/CancellableContinuation;)V
+}
+
+public final class kotlinx/coroutines/swing/SwingDispatcherKt {
+ public static final fun getSwing (Lkotlinx/coroutines/Dispatchers;)Lkotlinx/coroutines/swing/SwingDispatcher;
+}
+
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-test.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-test.txt
new file mode 100644
index 00000000..e3b1f73e
--- /dev/null
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-test.txt
@@ -0,0 +1,67 @@
+public abstract interface class kotlinx/coroutines/test/DelayController {
+ public abstract fun advanceTimeBy (J)J
+ public abstract fun advanceUntilIdle ()J
+ public abstract fun cleanupTestCoroutines ()V
+ public abstract fun getCurrentTime ()J
+ public abstract fun pauseDispatcher ()V
+ public abstract fun pauseDispatcher (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun resumeDispatcher ()V
+ public abstract fun runCurrent ()V
+}
+
+public final class kotlinx/coroutines/test/TestBuildersKt {
+ public static final fun runBlockingTest (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)V
+ public static final fun runBlockingTest (Lkotlinx/coroutines/test/TestCoroutineDispatcher;Lkotlin/jvm/functions/Function2;)V
+ public static final fun runBlockingTest (Lkotlinx/coroutines/test/TestCoroutineScope;Lkotlin/jvm/functions/Function2;)V
+ public static synthetic fun runBlockingTest$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+}
+
+public final class kotlinx/coroutines/test/TestCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher, kotlinx/coroutines/Delay, kotlinx/coroutines/test/DelayController {
+ public fun <init> ()V
+ public fun advanceTimeBy (J)J
+ public fun advanceUntilIdle ()J
+ public fun cleanupTestCoroutines ()V
+ public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun dispatchYield (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+ public fun getCurrentTime ()J
+ public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/DisposableHandle;
+ public fun pauseDispatcher ()V
+ public fun pauseDispatcher (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun resumeDispatcher ()V
+ public fun runCurrent ()V
+ public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/CancellableContinuation;)V
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class kotlinx/coroutines/test/TestCoroutineExceptionHandler : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/CoroutineExceptionHandler, kotlinx/coroutines/test/UncaughtExceptionCaptor {
+ public fun <init> ()V
+ public fun cleanupTestCoroutines ()V
+ public fun getUncaughtExceptions ()Ljava/util/List;
+ public fun handleException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V
+}
+
+public abstract interface class kotlinx/coroutines/test/TestCoroutineScope : kotlinx/coroutines/CoroutineScope, kotlinx/coroutines/test/DelayController, kotlinx/coroutines/test/UncaughtExceptionCaptor {
+ public abstract fun cleanupTestCoroutines ()V
+}
+
+public final class kotlinx/coroutines/test/TestCoroutineScopeKt {
+ public static final fun TestCoroutineScope (Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/test/TestCoroutineScope;
+ public static synthetic fun TestCoroutineScope$default (Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/test/TestCoroutineScope;
+}
+
+public final class kotlinx/coroutines/test/TestDispatchers {
+ public static final fun resetMain (Lkotlinx/coroutines/Dispatchers;)V
+ public static final fun setMain (Lkotlinx/coroutines/Dispatchers;Lkotlinx/coroutines/CoroutineDispatcher;)V
+}
+
+public abstract interface class kotlinx/coroutines/test/UncaughtExceptionCaptor {
+ public abstract fun cleanupTestCoroutines ()V
+ public abstract fun getUncaughtExceptions ()Ljava/util/List;
+}
+
+public final class kotlinx/coroutines/test/UncompletedCoroutinesError : java/lang/AssertionError {
+ public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
+ public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
diff --git a/binary-compatibility-validator/resources/api.properties b/binary-compatibility-validator/resources/api.properties
new file mode 100644
index 00000000..9fa115b9
--- /dev/null
+++ b/binary-compatibility-validator/resources/api.properties
@@ -0,0 +1,9 @@
+#
+# Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
+module.roots=/ integration reactive ui
+module.marker=build.gradle
+module.ignore=kotlinx-coroutines-rx-example stdlib-stubs benchmarks knit binary-compatibility-validator site publication-validator kotlinx-coroutines-bom
+
+packages.internal=kotlinx.coroutines.internal \ No newline at end of file
diff --git a/binary-compatibility-validator/src/PublicApiDump.kt b/binary-compatibility-validator/src/PublicApiDump.kt
new file mode 100644
index 00000000..343df34b
--- /dev/null
+++ b/binary-compatibility-validator/src/PublicApiDump.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.tools
+
+import org.objectweb.asm.*
+import org.objectweb.asm.tree.*
+import java.io.*
+import java.util.jar.*
+
+fun JarFile.classEntries() = entries().asSequence().filter {
+ !it.isDirectory && it.name.endsWith(".class") && !it.name.startsWith("META-INF/")
+}
+
+fun getBinaryAPI(jar: JarFile, visibilityMap: Map<String, ClassVisibility>): List<ClassBinarySignature> =
+ getBinaryAPI(jar.classEntries().map { entry -> jar.getInputStream(entry) }, visibilityMap)
+
+fun getBinaryAPI(
+ classStreams: Sequence<InputStream>,
+ visibilityMap: Map<String, ClassVisibility>
+): List<ClassBinarySignature> =
+ classStreams.map {
+ it.use { stream ->
+ val classNode = ClassNode()
+ ClassReader(stream).accept(classNode, ClassReader.SKIP_CODE)
+ classNode
+ }
+ }.map {
+ with(it) {
+ val classVisibility = visibilityMap[name]
+ val classAccess = AccessFlags(effectiveAccess and Opcodes.ACC_STATIC.inv())
+ val supertypes = listOf(superName) - "java/lang/Object" + interfaces.sorted()
+
+ val memberSignatures = (
+ fields.map {
+ with(it) {
+ FieldBinarySignature(
+ name,
+ desc,
+ isPublishedApi(),
+ AccessFlags(access)
+ )
+ }
+ } +
+ methods.map {
+ with(it) {
+ MethodBinarySignature(
+ name,
+ desc,
+ isPublishedApi(),
+ AccessFlags(access)
+ )
+ }
+ }
+ ).filter {
+ it.isEffectivelyPublic(classAccess, classVisibility)
+ }
+
+ ClassBinarySignature(
+ name,
+ superName,
+ outerClassName,
+ supertypes,
+ memberSignatures,
+ classAccess,
+ isEffectivelyPublic(classVisibility),
+ isFileOrMultipartFacade() || isDefaultImpls()
+ )
+ }
+ }.asIterable().sortedBy { it.name }
+
+
+fun List<ClassBinarySignature>.filterOutNonPublic(nonPublicPackages: List<String> = emptyList()): List<ClassBinarySignature> {
+ val nonPublicPaths = nonPublicPackages.map { it.replace('.', '/') + '/' }
+ val classByName = associateBy { it.name }
+
+ fun ClassBinarySignature.isInNonPublicPackage() =
+ nonPublicPaths.any { name.startsWith(it) }
+
+ fun ClassBinarySignature.isPublicAndAccessible(): Boolean =
+ isEffectivelyPublic &&
+ (outerName == null || classByName[outerName]?.let { outerClass ->
+ !(this.access.isProtected && outerClass.access.isFinal)
+ && outerClass.isPublicAndAccessible()
+ } ?: true)
+
+ fun supertypes(superName: String) = generateSequence({ classByName[superName] }, { classByName[it.superName] })
+
+ fun ClassBinarySignature.flattenNonPublicBases(): ClassBinarySignature {
+
+ val nonPublicSupertypes = supertypes(superName).takeWhile { !it.isPublicAndAccessible() }.toList()
+ if (nonPublicSupertypes.isEmpty())
+ return this
+
+ val inheritedStaticSignatures =
+ nonPublicSupertypes.flatMap { it.memberSignatures.filter { it.access.isStatic } }
+
+ // not covered the case when there is public superclass after chain of private superclasses
+ return this.copy(
+ memberSignatures = memberSignatures + inheritedStaticSignatures,
+ supertypes = supertypes - superName
+ )
+ }
+
+ return filter { !it.isInNonPublicPackage() && it.isPublicAndAccessible() }
+ .map { it.flattenNonPublicBases() }
+ .filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty() }
+}
+
+fun List<ClassBinarySignature>.dump() = dump(to = System.out)
+
+fun <T : Appendable> List<ClassBinarySignature>.dump(to: T): T = to.apply {
+ this@dump.forEach {
+ append(it.signature).appendln(" {")
+ it.memberSignatures.sortedWith(MEMBER_SORT_ORDER).forEach { append("\t").appendln(it.signature) }
+ appendln("}\n")
+ }
+}
+
diff --git a/binary-compatibility-validator/src/asmUtils.kt b/binary-compatibility-validator/src/asmUtils.kt
new file mode 100644
index 00000000..b14cb8d5
--- /dev/null
+++ b/binary-compatibility-validator/src/asmUtils.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.tools
+
+import org.objectweb.asm.*
+import org.objectweb.asm.tree.*
+
+val ACCESS_NAMES = mapOf(
+ Opcodes.ACC_PUBLIC to "public",
+ Opcodes.ACC_PROTECTED to "protected",
+ Opcodes.ACC_PRIVATE to "private",
+ Opcodes.ACC_STATIC to "static",
+ Opcodes.ACC_FINAL to "final",
+ Opcodes.ACC_ABSTRACT to "abstract",
+ Opcodes.ACC_SYNTHETIC to "synthetic",
+ Opcodes.ACC_INTERFACE to "interface",
+ Opcodes.ACC_ANNOTATION to "annotation")
+
+data class ClassBinarySignature(
+ val name: String,
+ val superName: String,
+ val outerName: String?,
+ val supertypes: List<String>,
+ val memberSignatures: List<MemberBinarySignature>,
+ val access: AccessFlags,
+ val isEffectivelyPublic: Boolean,
+ val isNotUsedWhenEmpty: Boolean) {
+
+ val signature: String
+ get() = "${access.getModifierString()} class $name" + if (supertypes.isEmpty()) "" else " : ${supertypes.joinToString()}"
+
+}
+
+
+interface MemberBinarySignature {
+ val name: String
+ val desc: String
+ val access: AccessFlags
+ val isPublishedApi: Boolean
+
+ fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?)
+ = access.isPublic && !(access.isProtected && classAccess.isFinal)
+ && (findMemberVisibility(classVisibility)?.isPublic(isPublishedApi) ?: true)
+
+ fun findMemberVisibility(classVisibility: ClassVisibility?)
+ = classVisibility?.members?.get(MemberSignature(name, desc))
+
+ val signature: String
+}
+
+data class MethodBinarySignature(
+ override val name: String,
+ override val desc: String,
+ override val isPublishedApi: Boolean,
+ override val access: AccessFlags) : MemberBinarySignature {
+ override val signature: String
+ get() = "${access.getModifierString()} fun $name $desc"
+
+ override fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?)
+ = super.isEffectivelyPublic(classAccess, classVisibility)
+ && !isAccessOrAnnotationsMethod()
+
+ private fun isAccessOrAnnotationsMethod() = access.isSynthetic && (name.startsWith("access\$") || name.endsWith("\$annotations"))
+}
+
+data class FieldBinarySignature(
+ override val name: String,
+ override val desc: String,
+ override val isPublishedApi: Boolean,
+ override val access: AccessFlags) : MemberBinarySignature {
+ override val signature: String
+ get() = "${access.getModifierString()} field $name $desc"
+
+ override fun findMemberVisibility(classVisibility: ClassVisibility?): MemberVisibility? {
+ val fieldVisibility = super.findMemberVisibility(classVisibility) ?: return null
+
+ // good case for 'satisfying': fieldVisibility.satisfying { it.isLateInit() }?.let { classVisibility?.findSetterForProperty(it) }
+ if (fieldVisibility.isLateInit()) {
+ classVisibility?.findSetterForProperty(fieldVisibility)?.let { return it }
+ }
+ return fieldVisibility
+ }
+}
+
+val MemberBinarySignature.kind: Int get() = when (this) {
+ is FieldBinarySignature -> 1
+ is MethodBinarySignature -> 2
+ else -> error("Unsupported $this")
+}
+
+val MEMBER_SORT_ORDER = compareBy<MemberBinarySignature>(
+ { it.kind },
+ { it.name },
+ { it.desc }
+)
+
+
+data class AccessFlags(val access: Int) {
+ val isPublic: Boolean get() = isPublic(access)
+ val isProtected: Boolean get() = isProtected(access)
+ val isStatic: Boolean get() = isStatic(access)
+ val isFinal: Boolean get() = isFinal(access)
+ val isSynthetic: Boolean get() = isSynthetic(access)
+
+ fun getModifiers(): List<String> = ACCESS_NAMES.entries.mapNotNull { if (access and it.key != 0) it.value else null }
+ fun getModifierString(): String = getModifiers().joinToString(" ")
+}
+
+fun isPublic(access: Int) = access and Opcodes.ACC_PUBLIC != 0 || access and Opcodes.ACC_PROTECTED != 0
+fun isProtected(access: Int) = access and Opcodes.ACC_PROTECTED != 0
+fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0
+fun isFinal(access: Int) = access and Opcodes.ACC_FINAL != 0
+fun isSynthetic(access: Int) = access and Opcodes.ACC_SYNTHETIC != 0
+
+
+fun ClassNode.isEffectivelyPublic(classVisibility: ClassVisibility?) =
+ isPublic(access)
+ && !isLocal()
+ && !isWhenMappings()
+ && (classVisibility?.isPublic(isPublishedApi()) ?: true)
+
+
+val ClassNode.innerClassNode: InnerClassNode? get() = innerClasses.singleOrNull { it.name == name }
+fun ClassNode.isLocal() = innerClassNode?.run { innerName == null && outerName == null} ?: false
+fun ClassNode.isInner() = innerClassNode != null
+fun ClassNode.isWhenMappings() = isSynthetic(access) && name.endsWith("\$WhenMappings")
+
+val ClassNode.effectiveAccess: Int get() = innerClassNode?.access ?: access
+val ClassNode.outerClassName: String? get() = innerClassNode?.outerName
+
+
+const val publishedApiAnnotationName = "kotlin/PublishedApi"
+fun ClassNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null
+fun MethodNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null
+fun FieldNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null
+
+
+private object KotlinClassKind {
+ const val FILE = 2
+ const val SYNTHETIC_CLASS = 3
+ const val MULTIPART_FACADE = 4
+
+ val FILE_OR_MULTIPART_FACADE_KINDS = listOf(FILE, MULTIPART_FACADE)
+}
+
+fun ClassNode.isFileOrMultipartFacade() = kotlinClassKind.let { it != null && it in KotlinClassKind.FILE_OR_MULTIPART_FACADE_KINDS }
+fun ClassNode.isDefaultImpls() = isInner() && name.endsWith("\$DefaultImpls") && kotlinClassKind == KotlinClassKind.SYNTHETIC_CLASS
+
+
+val ClassNode.kotlinClassKind: Int?
+ get() = findAnnotation("kotlin/Metadata", false)?.get("k") as Int?
+
+fun ClassNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible)
+fun MethodNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible)
+fun FieldNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible)
+
+operator fun AnnotationNode.get(key: String): Any? = values.annotationValue(key)
+
+private fun List<Any>.annotationValue(key: String): Any? {
+ for (index in (0 .. size / 2 - 1)) {
+ if (this[index*2] == key)
+ return this[index*2 + 1]
+ }
+ return null
+}
+
+private fun findAnnotation(annotationName: String, visibleAnnotations: List<AnnotationNode>?, invisibleAnnotations: List<AnnotationNode>?, includeInvisible: Boolean): AnnotationNode? =
+ visibleAnnotations?.firstOrNull { it.refersToName(annotationName) } ?:
+ if (includeInvisible) invisibleAnnotations?.firstOrNull { it.refersToName(annotationName) } else null
+
+fun AnnotationNode.refersToName(name: String) = desc.startsWith('L') && desc.endsWith(';') && desc.regionMatches(1, name, 0, name.length) \ No newline at end of file
diff --git a/binary-compatibility-validator/src/kotlinVisibilities.kt b/binary-compatibility-validator/src/kotlinVisibilities.kt
new file mode 100644
index 00000000..4322140c
--- /dev/null
+++ b/binary-compatibility-validator/src/kotlinVisibilities.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.tools
+
+import com.google.gson.internal.*
+import com.google.gson.stream.*
+import java.io.*
+
+data class ClassVisibility(val name: String, val visibility: String?, val members: Map<MemberSignature, MemberVisibility>)
+data class MemberVisibility(val member: MemberSignature, val declaration: String?, val visibility: String?)
+data class MemberSignature(val name: String, val desc: String)
+
+private fun isPublic(visibility: String?, isPublishedApi: Boolean) = visibility == null || visibility == "public" || visibility == "protected" || (isPublishedApi && visibility == "internal")
+fun ClassVisibility.isPublic(isPublishedApi: Boolean) = isPublic(visibility, isPublishedApi)
+fun MemberVisibility.isPublic(isPublishedApi: Boolean) = isPublic(visibility, isPublishedApi)
+
+fun MemberVisibility.isLateInit() = declaration != null && "lateinit var " in declaration
+
+private val varValPrefix = Regex("va[lr]\\s+")
+fun ClassVisibility.findSetterForProperty(property: MemberVisibility): MemberVisibility? {
+ // ad-hoc solution:
+ val declaration = property.declaration ?: return null
+ val match = varValPrefix.find(declaration) ?: return null
+ val name = declaration.substring(match.range.endInclusive + 1).substringBefore(':')
+ val setterName = "<set-$name>"
+ return members.values.find { it.declaration?.contains(setterName) ?: false }
+}
+
+fun readKotlinVisibilities(declarationFile: File): Map<String, ClassVisibility> {
+ val result = mutableListOf<ClassVisibility>()
+ declarationFile.bufferedReader().use { reader ->
+ val jsonReader = JsonReader(reader)
+ jsonReader.beginArray()
+ while (jsonReader.hasNext()) {
+ val classObject = Streams.parse(jsonReader).asJsonObject
+ result += with (classObject) {
+ val name = getAsJsonPrimitive("class").asString
+ val visibility = getAsJsonPrimitive("visibility")?.asString
+ val members = getAsJsonArray("members").map { it ->
+ with(it.asJsonObject) {
+ val name = getAsJsonPrimitive("name").asString
+ val desc = getAsJsonPrimitive("desc").asString
+ val declaration = getAsJsonPrimitive("declaration")?.asString
+ val visibility = getAsJsonPrimitive("visibility")?.asString
+ MemberVisibility(MemberSignature(name, desc), declaration, visibility)
+ }
+ }
+ ClassVisibility(name, visibility, members.associateByTo(hashMapOf()) { it.member })
+ }
+ }
+ jsonReader.endArray()
+ }
+
+ return result.associateByTo(hashMapOf()) { it.name }
+}
diff --git a/binary-compatibility-validator/test/CasesPublicAPITest.kt b/binary-compatibility-validator/test/CasesPublicAPITest.kt
new file mode 100644
index 00000000..f0212e70
--- /dev/null
+++ b/binary-compatibility-validator/test/CasesPublicAPITest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.tools
+
+import org.junit.*
+import org.junit.rules.*
+import java.io.*
+
+class CasesPublicAPITest {
+
+ companion object {
+ val visibilities by lazy { readKotlinVisibilities(File(System.getProperty("testCasesDeclarations")!!)) }
+
+ val baseClassPaths: List<File> =
+ System.getProperty("testCasesClassesDirs")
+ .let { requireNotNull(it) { "Specify testCasesClassesDirs with a system property" } }
+ .split(File.pathSeparator)
+ .map { File(it, "cases").canonicalFile }
+ val baseOutputPath = File("test/cases")
+ }
+
+ @Rule
+ @JvmField
+ val testName = TestName()
+
+ @Test
+ fun companions() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun inline() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun interfaces() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun internal() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun java() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun localClasses() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun nestedClasses() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun private() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun protected() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun public() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun special() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+ @Test
+ fun whenMappings() {
+ snapshotAPIAndCompare(testName.methodName)
+ }
+
+
+ private fun snapshotAPIAndCompare(testClassRelativePath: String) {
+ val testClassPaths = baseClassPaths.map { it.resolve(testClassRelativePath) }
+ val testClasses = testClassPaths.flatMap { it.listFiles().orEmpty().asIterable() }
+ check(testClasses.isNotEmpty()) { "No class files are found in paths: $testClassPaths" }
+ val testClassStreams = testClasses.asSequence().filter { it.name.endsWith(".class") }.map { it.inputStream() }
+ val api = getBinaryAPI(testClassStreams, visibilities).filterOutNonPublic()
+ val target = baseOutputPath.resolve(testClassRelativePath).resolve(testName.methodName + ".txt")
+ api.dumpAndCompareWith(target)
+ }
+}
diff --git a/binary-compatibility-validator/test/PublicApiTest.kt b/binary-compatibility-validator/test/PublicApiTest.kt
new file mode 100644
index 00000000..fb4f55cc
--- /dev/null
+++ b/binary-compatibility-validator/test/PublicApiTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.tools
+
+import org.junit.*
+import org.junit.runner.*
+import org.junit.runners.*
+import java.io.*
+import java.util.*
+import java.util.jar.*
+import kotlin.collections.ArrayList
+
+@RunWith(Parameterized::class)
+class PublicApiTest(
+ private val rootDir: String,
+ private val moduleName: String
+) {
+ companion object {
+ private val apiProps = ClassLoader.getSystemClassLoader()
+ .getResource("api.properties").openStream().use { Properties().apply { load(it) } }
+ private val nonPublicPackages = apiProps.getProperty("packages.internal")!!.split(" ")
+
+ @Parameterized.Parameters(name = "{1}")
+ @JvmStatic
+ fun modules(): List<Array<Any>> {
+ val moduleRoots = apiProps.getProperty("module.roots").split(" ")
+ val moduleMarker = apiProps.getProperty("module.marker")!!
+ val moduleIgnore = apiProps.getProperty("module.ignore")!!.split(" ").toSet()
+ val modules = ArrayList<Array<Any>>()
+ for (rootDir in moduleRoots) {
+ File("../$rootDir").listFiles( FileFilter { it.isDirectory })?.forEach { dir ->
+ if (dir.name !in moduleIgnore && File(dir, moduleMarker).exists()) {
+ modules += arrayOf<Any>(rootDir, dir.name)
+ }
+ }
+ }
+ return modules
+ }
+ }
+
+ @Test
+ fun testApi() {
+ val libsDir = File("../$rootDir/$moduleName/build/libs").absoluteFile.normalize()
+ val jarPath = getJarPath(libsDir)
+ val kotlinJvmMappingsFiles = listOf(libsDir.resolve("../visibilities.json"))
+ val visibilities =
+ kotlinJvmMappingsFiles
+ .map { readKotlinVisibilities(it) }
+ .reduce { m1, m2 -> m1 + m2 }
+ JarFile(jarPath).use { jarFile ->
+ val api = getBinaryAPI(jarFile, visibilities).filterOutNonPublic(nonPublicPackages)
+ api.dumpAndCompareWith(File("reference-public-api").resolve("$moduleName.txt"))
+ // check for atomicfu leaks
+ jarFile.checkForAtomicFu()
+ }
+ }
+
+ private fun getJarPath(libsDir: File): File {
+ val regex = Regex("$moduleName-.+\\.jar")
+ var files = (libsDir.listFiles() ?: throw Exception("Cannot list files in $libsDir"))
+ .filter { it.name.let {
+ it matches regex
+ && !it.endsWith("-sources.jar")
+ && !it.endsWith("-javadoc.jar")
+ && !it.endsWith("-tests.jar")}
+ && !it.name.contains("-metadata-")}
+ if (files.size > 1) // maybe multiplatform?
+ files = files.filter { it.name.startsWith("$moduleName-jvm-") }
+ return files.singleOrNull() ?:
+ error("No single file matching $regex in $libsDir:\n${files.joinToString("\n")}")
+ }
+}
+
+private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
+
+private fun JarFile.checkForAtomicFu() {
+ val foundClasses = mutableListOf<String>()
+ for (e in entries()) {
+ if (!e.name.endsWith(".class")) continue
+ val bytes = getInputStream(e).use { it.readBytes() }
+ loop@for (i in 0 until bytes.size - ATOMIC_FU_REF.size) {
+ for (j in 0 until ATOMIC_FU_REF.size) {
+ if (bytes[i + j] != ATOMIC_FU_REF[j]) continue@loop
+ }
+ foundClasses += e.name // report error at the end with all class names
+ break@loop
+ }
+ }
+ if (foundClasses.isNotEmpty()) {
+ error("Found references to atomicfu in jar file $name in the following class files: ${
+ foundClasses.joinToString("") { "\n\t\t" + it }
+ }")
+ }
+}
diff --git a/binary-compatibility-validator/test/cases/companions/companions.kt b/binary-compatibility-validator/test/cases/companions/companions.kt
new file mode 100644
index 00000000..ef59c6fe
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/companions/companions.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.companions
+
+
+object PublicClasses {
+ class PublicCompanion {
+ companion object
+ }
+
+ class ProtectedCompanion {
+ protected companion object
+ }
+
+ abstract class AbstractProtectedCompanion {
+ protected companion object
+ }
+
+ class InternalCompanion {
+ internal companion object
+ }
+
+ class PrivateCompanion {
+ private companion object
+ }
+}
+
+object PublicInterfaces {
+ interface PublicCompanion {
+ companion object
+ }
+
+ interface PrivateCompanion {
+ private companion object
+ }
+}
+
+
+
+object InternalClasses {
+ internal class PublicCompanion {
+ companion object
+ }
+
+ internal class ProtectedCompanion {
+ protected companion object
+ }
+
+ internal abstract class AbstractProtectedCompanion {
+ protected companion object
+ }
+
+ internal class InternalCompanion {
+ internal companion object
+ }
+
+ internal class PrivateCompanion {
+ private companion object
+ }
+}
+
+object InternalInterfaces {
+ internal interface PublicCompanion {
+ companion object
+ }
+
+ internal interface PrivateCompanion {
+ private companion object
+ }
+}
+
+
+object PrivateClasses {
+ private class PublicCompanion {
+ companion object
+ }
+
+ private class ProtectedCompanion {
+ protected companion object
+ }
+
+ private abstract class AbstractProtectedCompanion {
+ protected companion object
+ }
+
+ private class InternalCompanion {
+ internal companion object
+ }
+
+ private class PrivateCompanion {
+ private companion object
+ }
+}
+
+object PrivateInterfaces {
+ private interface PublicCompanion {
+ companion object
+ }
+
+ private interface PrivateCompanion {
+ private companion object
+ }
+}
+
diff --git a/binary-compatibility-validator/test/cases/companions/companions.txt b/binary-compatibility-validator/test/cases/companions/companions.txt
new file mode 100644
index 00000000..691907ba
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/companions/companions.txt
@@ -0,0 +1,66 @@
+public final class cases/companions/InternalClasses {
+ public static final field INSTANCE Lcases/companions/InternalClasses;
+}
+
+public final class cases/companions/InternalInterfaces {
+ public static final field INSTANCE Lcases/companions/InternalInterfaces;
+}
+
+public final class cases/companions/PrivateClasses {
+ public static final field INSTANCE Lcases/companions/PrivateClasses;
+}
+
+public final class cases/companions/PrivateInterfaces {
+ public static final field INSTANCE Lcases/companions/PrivateInterfaces;
+}
+
+public final class cases/companions/PublicClasses {
+ public static final field INSTANCE Lcases/companions/PublicClasses;
+}
+
+public abstract class cases/companions/PublicClasses$AbstractProtectedCompanion {
+ public static final field Companion Lcases/companions/PublicClasses$AbstractProtectedCompanion$Companion;
+ public fun <init> ()V
+}
+
+protected final class cases/companions/PublicClasses$AbstractProtectedCompanion$Companion {
+}
+
+public final class cases/companions/PublicClasses$InternalCompanion {
+ public static final field Companion Lcases/companions/PublicClasses$InternalCompanion$Companion;
+ public fun <init> ()V
+}
+
+public final class cases/companions/PublicClasses$PrivateCompanion {
+ public static final field Companion Lcases/companions/PublicClasses$PrivateCompanion$Companion;
+ public fun <init> ()V
+}
+
+public final class cases/companions/PublicClasses$ProtectedCompanion {
+ public static final field Companion Lcases/companions/PublicClasses$ProtectedCompanion$Companion;
+ public fun <init> ()V
+}
+
+public final class cases/companions/PublicClasses$PublicCompanion {
+ public static final field Companion Lcases/companions/PublicClasses$PublicCompanion$Companion;
+ public fun <init> ()V
+}
+
+public final class cases/companions/PublicClasses$PublicCompanion$Companion {
+}
+
+public final class cases/companions/PublicInterfaces {
+ public static final field INSTANCE Lcases/companions/PublicInterfaces;
+}
+
+public abstract interface class cases/companions/PublicInterfaces$PrivateCompanion {
+ public static final field Companion Lcases/companions/PublicInterfaces$PrivateCompanion$Companion;
+}
+
+public abstract interface class cases/companions/PublicInterfaces$PublicCompanion {
+ public static final field Companion Lcases/companions/PublicInterfaces$PublicCompanion$Companion;
+}
+
+public final class cases/companions/PublicInterfaces$PublicCompanion$Companion {
+}
+
diff --git a/binary-compatibility-validator/test/cases/inline/inline.txt b/binary-compatibility-validator/test/cases/inline/inline.txt
new file mode 100644
index 00000000..4961dbcf
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/inline/inline.txt
@@ -0,0 +1,9 @@
+public final class cases/inline/InlineExposedKt {
+ public static final fun exposedForInline ()V
+}
+
+public final class cases/inline/InternalClassExposed {
+ public fun <init> ()V
+ public final fun funExposed ()V
+}
+
diff --git a/binary-compatibility-validator/test/cases/inline/inlineExposed.kt b/binary-compatibility-validator/test/cases/inline/inlineExposed.kt
new file mode 100644
index 00000000..2e017b38
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/inline/inlineExposed.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.inline
+
+@PublishedApi
+internal fun exposedForInline() {}
+
+@PublishedApi
+internal class InternalClassExposed
+ @PublishedApi
+ internal constructor() {
+
+ @PublishedApi
+ internal fun funExposed() {}
+
+ // TODO: Cover unsupported cases: requires correctly reflecting annotations from properties
+ /*
+ @PublishedApi
+ internal var propertyExposed: String? = null
+
+ @JvmField
+ @PublishedApi
+ internal var fieldExposed: String? = null
+ */
+
+}
diff --git a/binary-compatibility-validator/test/cases/inline/inlineOnly.kt b/binary-compatibility-validator/test/cases/inline/inlineOnly.kt
new file mode 100644
index 00000000..9c1f01eb
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/inline/inlineOnly.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.inline
+
+@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+@kotlin.internal.InlineOnly
+public inline fun inlineOnly(f: () -> Unit) = f() \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/interfaces/interfaceWithEmptyImpls.kt b/binary-compatibility-validator/test/cases/interfaces/interfaceWithEmptyImpls.kt
new file mode 100644
index 00000000..96a1628c
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/interfaces/interfaceWithEmptyImpls.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.interfaces
+
+public interface EmptyImpls {
+ @SinceKotlin("1.1")
+ val property: String
+}
diff --git a/binary-compatibility-validator/test/cases/interfaces/interfaceWithImpls.kt b/binary-compatibility-validator/test/cases/interfaces/interfaceWithImpls.kt
new file mode 100644
index 00000000..d2e4ad0c
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/interfaces/interfaceWithImpls.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.interfaces
+
+public interface BaseWithImpl {
+ fun foo() = 42
+}
+
+public interface DerivedWithImpl : BaseWithImpl {
+ override fun foo(): Int {
+ return super.foo() + 1
+ }
+}
+
+public interface DerivedWithoutImpl : BaseWithImpl
+
diff --git a/binary-compatibility-validator/test/cases/interfaces/interfaces.txt b/binary-compatibility-validator/test/cases/interfaces/interfaces.txt
new file mode 100644
index 00000000..4f37b42f
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/interfaces/interfaces.txt
@@ -0,0 +1,27 @@
+public abstract interface class cases/interfaces/BaseWithImpl {
+ public abstract fun foo ()I
+}
+
+public final class cases/interfaces/BaseWithImpl$DefaultImpls {
+ public static fun foo (Lcases/interfaces/BaseWithImpl;)I
+}
+
+public abstract interface class cases/interfaces/DerivedWithImpl : cases/interfaces/BaseWithImpl {
+ public abstract fun foo ()I
+}
+
+public final class cases/interfaces/DerivedWithImpl$DefaultImpls {
+ public static fun foo (Lcases/interfaces/DerivedWithImpl;)I
+}
+
+public abstract interface class cases/interfaces/DerivedWithoutImpl : cases/interfaces/BaseWithImpl {
+}
+
+public final class cases/interfaces/DerivedWithoutImpl$DefaultImpls {
+ public static fun foo (Lcases/interfaces/DerivedWithoutImpl;)I
+}
+
+public abstract interface class cases/interfaces/EmptyImpls {
+ public abstract fun getProperty ()Ljava/lang/String;
+}
+
diff --git a/binary-compatibility-validator/test/cases/internal/internal.txt b/binary-compatibility-validator/test/cases/internal/internal.txt
new file mode 100644
index 00000000..2ebfa3af
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/internal/internal.txt
@@ -0,0 +1,3 @@
+public final class cases/internal/PublicClass {
+}
+
diff --git a/binary-compatibility-validator/test/cases/internal/internalClass.kt b/binary-compatibility-validator/test/cases/internal/internalClass.kt
new file mode 100644
index 00000000..2410b657
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/internal/internalClass.kt
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.internal
+
+internal class InternalClass {
+ public val property = 1
+
+ public fun function() = property
+} \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/internal/internalMultifile1.kt b/binary-compatibility-validator/test/cases/internal/internalMultifile1.kt
new file mode 100644
index 00000000..b4622b3a
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/internal/internalMultifile1.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmName("MultifileKt")
+@file:JvmMultifileClass
+package cases.internal
+
+internal fun internalFun1() = internalVal
diff --git a/binary-compatibility-validator/test/cases/internal/internalMultifile2.kt b/binary-compatibility-validator/test/cases/internal/internalMultifile2.kt
new file mode 100644
index 00000000..c375c01e
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/internal/internalMultifile2.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmName("MultifileKt")
+@file:JvmMultifileClass
+package cases.internal
+
+internal val internalVal = "Internal"
diff --git a/binary-compatibility-validator/test/cases/internal/internalPart.kt b/binary-compatibility-validator/test/cases/internal/internalPart.kt
new file mode 100644
index 00000000..b95ba683
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/internal/internalPart.kt
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.internal
+
+internal fun internalFun() {
+
+}
+
+// TODO: var, val, const \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/internal/publicClassInternalMember.kt b/binary-compatibility-validator/test/cases/internal/publicClassInternalMember.kt
new file mode 100644
index 00000000..11aac7c5
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/internal/publicClassInternalMember.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.internal
+
+public class PublicClass internal constructor() {
+
+ internal val property = 1
+
+ internal fun function() = property
+
+}
diff --git a/binary-compatibility-validator/test/cases/java/Facade.java b/binary-compatibility-validator/test/cases/java/Facade.java
new file mode 100644
index 00000000..d11c16cd
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/java/Facade.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.java;
+
+class Part1 {
+ public static void publicMethod(int param) { }
+
+ public static class Part2 extends Part1 {
+ public static void publicMethod(String param) { }
+ }
+}
+
+
+public class Facade extends Part1.Part2 { }
diff --git a/binary-compatibility-validator/test/cases/java/java.txt b/binary-compatibility-validator/test/cases/java/java.txt
new file mode 100644
index 00000000..75bfd317
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/java/java.txt
@@ -0,0 +1,6 @@
+public class cases/java/Facade {
+ public fun <init> ()V
+ public static fun publicMethod (I)V
+ public static fun publicMethod (Ljava/lang/String;)V
+}
+
diff --git a/binary-compatibility-validator/test/cases/localClasses/fromStdlib.kt b/binary-compatibility-validator/test/cases/localClasses/fromStdlib.kt
new file mode 100644
index 00000000..29f6994e
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/localClasses/fromStdlib.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.localClasses
+
+private val COMPARER = compareBy<String> { it.length } \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/localClasses/lambdas.kt b/binary-compatibility-validator/test/cases/localClasses/lambdas.kt
new file mode 100644
index 00000000..a99c4497
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/localClasses/lambdas.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.localClasses
+
+
+class L {
+ internal fun a(lambda: () -> Unit) = lambda()
+
+ @Suppress("NOTHING_TO_INLINE")
+ internal inline fun inlineLambda() {
+ a {
+ println("OK")
+ }
+ }
+}
+
+fun box() {
+ L().inlineLambda()
+}
+
+
+// TODO: inline lambda from stdlib \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/localClasses/localClasses.kt b/binary-compatibility-validator/test/cases/localClasses/localClasses.kt
new file mode 100644
index 00000000..1af4b77d
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/localClasses/localClasses.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.localClasses
+
+class A {
+ fun a() : String {
+ class B() {
+ fun s() : String = "OK"
+
+ inner class C {}
+
+ }
+ return B().s()
+ }
+}
+
+
+class B {
+ fun a(p: String) : String {
+ class B() {
+ fun s() : String = p
+ }
+ return B().s()
+ }
+}
diff --git a/binary-compatibility-validator/test/cases/localClasses/localClasses.txt b/binary-compatibility-validator/test/cases/localClasses/localClasses.txt
new file mode 100644
index 00000000..da0d668c
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/localClasses/localClasses.txt
@@ -0,0 +1,18 @@
+public final class cases/localClasses/A {
+ public fun <init> ()V
+ public final fun a ()Ljava/lang/String;
+}
+
+public final class cases/localClasses/B {
+ public fun <init> ()V
+ public final fun a (Ljava/lang/String;)Ljava/lang/String;
+}
+
+public final class cases/localClasses/L {
+ public fun <init> ()V
+}
+
+public final class cases/localClasses/LambdasKt {
+ public static final fun box ()V
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/internalClass.kt b/binary-compatibility-validator/test/cases/nestedClasses/internalClass.kt
new file mode 100644
index 00000000..7af2dda9
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/internalClass.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+internal class InternalClass {
+ public object ObjPublic
+ internal object ObjInternal
+ protected object ObjProtected
+ private object ObjPrivate
+
+ public class NestedPublic
+ internal class NestedInternal
+ protected class NestedProtected
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ internal interface NestedInternalInterface
+ protected interface NestedProtectedInterface
+ private interface NestedPrivateInterface
+
+ public inner class InnerPublic
+ internal inner class InnerInternal
+ protected inner class InnerProtected
+ private inner class InnerPrivate
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/internalInterface.kt b/binary-compatibility-validator/test/cases/nestedClasses/internalInterface.kt
new file mode 100644
index 00000000..a0affc53
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/internalInterface.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+internal interface InternalInterface {
+ public object ObjPublic
+ private object ObjPrivate
+
+ public class NestedPublic
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ private interface NestedPrivateInterface
+
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/internalObject.kt b/binary-compatibility-validator/test/cases/nestedClasses/internalObject.kt
new file mode 100644
index 00000000..ca3fd834
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/internalObject.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+internal object InternalObject {
+
+ public object ObjPublic
+ internal object ObjInternal
+ private object ObjPrivate
+
+ public class NestedPublic
+ internal class NestedInternal
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ internal interface NestedInternalInterface
+ private interface NestedPrivateInterface
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/nestedClasses.txt b/binary-compatibility-validator/test/cases/nestedClasses/nestedClasses.txt
new file mode 100644
index 00000000..4ba38c78
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/nestedClasses.txt
@@ -0,0 +1,86 @@
+public abstract class cases/nestedClasses/PublicAbstractClass {
+ public fun <init> ()V
+}
+
+protected final class cases/nestedClasses/PublicAbstractClass$InnerProtected {
+ public fun <init> (Lcases/nestedClasses/PublicAbstractClass;)V
+}
+
+protected final class cases/nestedClasses/PublicAbstractClass$NestedProtected {
+ public fun <init> ()V
+}
+
+protected abstract interface class cases/nestedClasses/PublicAbstractClass$NestedProtectedInterface {
+}
+
+protected final class cases/nestedClasses/PublicAbstractClass$ObjProtected {
+ public static final field INSTANCE Lcases/nestedClasses/PublicAbstractClass$ObjProtected;
+}
+
+public final class cases/nestedClasses/PublicClass {
+ public fun <init> ()V
+}
+
+public final class cases/nestedClasses/PublicClass$InnerPublic {
+ public fun <init> (Lcases/nestedClasses/PublicClass;)V
+}
+
+public final class cases/nestedClasses/PublicClass$NestedPublic {
+ public fun <init> ()V
+}
+
+public abstract interface class cases/nestedClasses/PublicClass$NestedPublicInterface {
+}
+
+public final class cases/nestedClasses/PublicClass$ObjPublic {
+ public static final field INSTANCE Lcases/nestedClasses/PublicClass$ObjPublic;
+}
+
+public abstract interface class cases/nestedClasses/PublicInterface {
+}
+
+public final class cases/nestedClasses/PublicInterface$NestedPublic {
+ public fun <init> ()V
+}
+
+public abstract interface class cases/nestedClasses/PublicInterface$NestedPublicInterface {
+}
+
+public final class cases/nestedClasses/PublicInterface$ObjPublic {
+ public static final field INSTANCE Lcases/nestedClasses/PublicInterface$ObjPublic;
+}
+
+public final class cases/nestedClasses/PublicObject {
+ public static final field INSTANCE Lcases/nestedClasses/PublicObject;
+}
+
+public final class cases/nestedClasses/PublicObject$NestedPublic {
+ public fun <init> ()V
+}
+
+public abstract interface class cases/nestedClasses/PublicObject$NestedPublicInterface {
+}
+
+public final class cases/nestedClasses/PublicObject$ObjPublic {
+ public static final field INSTANCE Lcases/nestedClasses/PublicObject$ObjPublic;
+}
+
+public class cases/nestedClasses/PublicOpenClass {
+ public fun <init> ()V
+}
+
+protected final class cases/nestedClasses/PublicOpenClass$InnerProtected {
+ public fun <init> (Lcases/nestedClasses/PublicOpenClass;)V
+}
+
+protected final class cases/nestedClasses/PublicOpenClass$NestedProtected {
+ public fun <init> ()V
+}
+
+protected abstract interface class cases/nestedClasses/PublicOpenClass$NestedProtectedInterface {
+}
+
+protected final class cases/nestedClasses/PublicOpenClass$ObjProtected {
+ public static final field INSTANCE Lcases/nestedClasses/PublicOpenClass$ObjProtected;
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/privateClass.kt b/binary-compatibility-validator/test/cases/nestedClasses/privateClass.kt
new file mode 100644
index 00000000..1e1fa8a8
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/privateClass.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+private class PrivateClass {
+ public object ObjPublic
+ internal object ObjInternal
+ protected object ObjProtected
+ private object ObjPrivate
+
+ public class NestedPublic
+ internal class NestedInternal
+ protected class NestedProtected
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ internal interface NestedInternalInterface
+ protected interface NestedProtectedInterface
+ private interface NestedPrivateInterface
+
+ public inner class InnerPublic
+ internal inner class InnerInternal
+ protected inner class InnerProtected
+ private inner class InnerPrivate
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/privateInterface.kt b/binary-compatibility-validator/test/cases/nestedClasses/privateInterface.kt
new file mode 100644
index 00000000..8c936a1f
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/privateInterface.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+private interface PrivateInterface {
+ public object ObjPublic
+ private object ObjPrivate
+
+ public class NestedPublic
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ private interface NestedPrivateInterface
+
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/privateObject.kt b/binary-compatibility-validator/test/cases/nestedClasses/privateObject.kt
new file mode 100644
index 00000000..4251d25e
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/privateObject.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+private object PrivateObject {
+
+ public object ObjPublic
+ internal object ObjInternal
+ private object ObjPrivate
+
+ public class NestedPublic
+ internal class NestedInternal
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ internal interface NestedInternalInterface
+ private interface NestedPrivateInterface
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/publicAbstractClass.kt b/binary-compatibility-validator/test/cases/nestedClasses/publicAbstractClass.kt
new file mode 100644
index 00000000..1da59a5b
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/publicAbstractClass.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+public abstract class PublicAbstractClass {
+ protected object ObjProtected
+
+ protected class NestedProtected
+
+ protected interface NestedProtectedInterface
+
+ protected inner class InnerProtected
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/publicClass.kt b/binary-compatibility-validator/test/cases/nestedClasses/publicClass.kt
new file mode 100644
index 00000000..64f487d9
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/publicClass.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+public class PublicClass {
+ public object ObjPublic
+ internal object ObjInternal
+ protected object ObjProtected
+ private object ObjPrivate
+
+ public class NestedPublic
+ internal class NestedInternal
+ protected class NestedProtected
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ internal interface NestedInternalInterface
+ protected interface NestedProtectedInterface
+ private interface NestedPrivateInterface
+
+ public inner class InnerPublic
+ internal inner class InnerInternal
+ protected inner class InnerProtected
+ private inner class InnerPrivate
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/publicInterface.kt b/binary-compatibility-validator/test/cases/nestedClasses/publicInterface.kt
new file mode 100644
index 00000000..3ee24f54
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/publicInterface.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+public interface PublicInterface {
+ public object ObjPublic
+ private object ObjPrivate
+
+ public class NestedPublic
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ private interface NestedPrivateInterface
+
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/publicObject.kt b/binary-compatibility-validator/test/cases/nestedClasses/publicObject.kt
new file mode 100644
index 00000000..6ce2bcdd
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/publicObject.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+public object PublicObject {
+
+ public object ObjPublic
+ internal object ObjInternal
+ private object ObjPrivate
+
+ public class NestedPublic
+ internal class NestedInternal
+ private class NestedPrivate
+
+ public interface NestedPublicInterface
+ internal interface NestedInternalInterface
+ private interface NestedPrivateInterface
+}
+
diff --git a/binary-compatibility-validator/test/cases/nestedClasses/publicOpenClass.kt b/binary-compatibility-validator/test/cases/nestedClasses/publicOpenClass.kt
new file mode 100644
index 00000000..1c9edef5
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/nestedClasses/publicOpenClass.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.nestedClasses
+
+public open class PublicOpenClass {
+ protected object ObjProtected
+
+ protected class NestedProtected
+
+ protected interface NestedProtectedInterface
+
+ protected inner class InnerProtected
+}
+
diff --git a/binary-compatibility-validator/test/cases/private/private.txt b/binary-compatibility-validator/test/cases/private/private.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/private/private.txt
diff --git a/binary-compatibility-validator/test/cases/private/privateClassMembers.kt b/binary-compatibility-validator/test/cases/private/privateClassMembers.kt
new file mode 100644
index 00000000..92dd6183
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/private/privateClassMembers.kt
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.private
+
+private open class PrivateClass public constructor() {
+ internal val internalVal = 1
+
+ protected fun protectedFun() = internalVal
+}
diff --git a/binary-compatibility-validator/test/cases/private/privateMultifile1.kt b/binary-compatibility-validator/test/cases/private/privateMultifile1.kt
new file mode 100644
index 00000000..8f7535f8
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/private/privateMultifile1.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmName("MultifileKt")
+@file:JvmMultifileClass
+package cases.private
+
+private val privateVal: Any? = 1
+private var privateVar: Any? = 1
+
+
diff --git a/binary-compatibility-validator/test/cases/private/privateMultifile2.kt b/binary-compatibility-validator/test/cases/private/privateMultifile2.kt
new file mode 100644
index 00000000..ee0a0804
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/private/privateMultifile2.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmName("MultifileKt")
+@file:JvmMultifileClass
+package cases.private
+
+
+// const
+private const val privateConst: Int = 4
+
+// fun
+@Suppress("UNUSED_PARAMETER")
+private fun privateFun(x: Any) {}
+
+
+private class PrivateClassInMultifile {
+ internal fun accessUsage() {
+ privateFun(privateConst)
+ }
+
+}
diff --git a/binary-compatibility-validator/test/cases/private/privatePart.kt b/binary-compatibility-validator/test/cases/private/privatePart.kt
new file mode 100644
index 00000000..93d4ec14
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/private/privatePart.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.private
+
+// properties
+private val privateVal: Any? = 1
+private var privateVar: Any? = 1
+
+// constants
+
+private const val privateConst: Int = 4
+
+// fun
+
+@Suppress("UNUSED_PARAMETER")
+private fun privateFun(a: Any?) = privateConst
+
+// access
+private class PrivateClassInPart {
+ internal fun accessUsage() {
+ privateFun(privateVal)
+ privateFun(privateVar)
+ privateFun(privateConst)
+ }
+
+} \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/protected/protected.txt b/binary-compatibility-validator/test/cases/protected/protected.txt
new file mode 100644
index 00000000..3c28d7e1
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/protected/protected.txt
@@ -0,0 +1,19 @@
+public abstract class cases/protected/PublicAbstractClass {
+ protected fun <init> ()V
+ protected abstract fun getProtectedVal ()I
+ protected abstract fun getProtectedVar ()Ljava/lang/Object;
+ protected abstract fun protectedFun ()V
+ protected abstract fun setProtectedVar (Ljava/lang/Object;)V
+}
+
+public final class cases/protected/PublicFinalClass {
+}
+
+public class cases/protected/PublicOpenClass {
+ protected fun <init> ()V
+ protected final fun getProtectedVal ()I
+ protected final fun getProtectedVar ()I
+ protected final fun protectedFun ()I
+ protected final fun setProtectedVar (I)V
+}
+
diff --git a/binary-compatibility-validator/test/cases/protected/protectedInAbstract.kt b/binary-compatibility-validator/test/cases/protected/protectedInAbstract.kt
new file mode 100644
index 00000000..ab4e26fe
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/protected/protectedInAbstract.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.protected
+
+public abstract class PublicAbstractClass protected constructor() {
+ protected abstract val protectedVal: Int
+ protected abstract var protectedVar: Any?
+
+ protected abstract fun protectedFun()
+}
diff --git a/binary-compatibility-validator/test/cases/protected/protectedInFinal.kt b/binary-compatibility-validator/test/cases/protected/protectedInFinal.kt
new file mode 100644
index 00000000..419e3f42
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/protected/protectedInFinal.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.protected
+
+public class PublicFinalClass protected constructor() {
+ protected val protectedVal = 1
+ protected var protectedVar = 2
+
+ protected fun protectedFun() = protectedVal
+}
diff --git a/binary-compatibility-validator/test/cases/protected/protectedInOpen.kt b/binary-compatibility-validator/test/cases/protected/protectedInOpen.kt
new file mode 100644
index 00000000..ee686835
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/protected/protectedInOpen.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.protected
+
+public open class PublicOpenClass protected constructor() {
+ protected val protectedVal = 1
+ protected var protectedVar = 2
+
+ protected fun protectedFun() = protectedVal
+}
diff --git a/binary-compatibility-validator/test/cases/public/public.txt b/binary-compatibility-validator/test/cases/public/public.txt
new file mode 100644
index 00000000..c9dd5dd2
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/public/public.txt
@@ -0,0 +1,9 @@
+public final class cases/public/MultifileKt {
+ public static final fun getPublicVal ()Ljava/lang/String;
+ public static final fun publicFun1 ()Ljava/lang/String;
+}
+
+public final class cases/public/PublicPartKt {
+ public static final fun publicFun ()V
+}
+
diff --git a/binary-compatibility-validator/test/cases/public/publicMultifile1.kt b/binary-compatibility-validator/test/cases/public/publicMultifile1.kt
new file mode 100644
index 00000000..ccc6823c
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/public/publicMultifile1.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmName("MultifileKt")
+@file:JvmMultifileClass
+package cases.public
+
+public fun publicFun1() = publicVal
diff --git a/binary-compatibility-validator/test/cases/public/publicMultifile2.kt b/binary-compatibility-validator/test/cases/public/publicMultifile2.kt
new file mode 100644
index 00000000..174bd43d
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/public/publicMultifile2.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmName("MultifileKt")
+@file:JvmMultifileClass
+package cases.public
+
+public val publicVal = "Public"
diff --git a/binary-compatibility-validator/test/cases/public/publicPart.kt b/binary-compatibility-validator/test/cases/public/publicPart.kt
new file mode 100644
index 00000000..3ad78362
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/public/publicPart.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.public
+
+public fun publicFun() {
+
+} \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/special/hidden.kt b/binary-compatibility-validator/test/cases/special/hidden.kt
new file mode 100644
index 00000000..cb389b8d
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/special/hidden.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.special
+
+@Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
+public class HiddenClass
+ @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
+ public constructor() {
+
+ @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
+ val hiddenVal = 1
+
+ @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
+ var hiddenVar = 2
+
+ @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
+ fun hiddenFun() {}
+
+ public var varWithHiddenAccessors: String = ""
+ @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
+ get
+ @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
+ set
+}
+
+@Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
+fun hiddenTopLevelFun() {}
diff --git a/binary-compatibility-validator/test/cases/special/internalLateinitMember.kt b/binary-compatibility-validator/test/cases/special/internalLateinitMember.kt
new file mode 100644
index 00000000..e9819fa4
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/special/internalLateinitMember.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.special
+
+public class ClassWithLateInitMembers internal constructor() {
+
+ public lateinit var publicLateInitWithInternalSet: String
+ internal set
+
+ internal lateinit var internalLateInit: String
+
+} \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/special/jvmField.kt b/binary-compatibility-validator/test/cases/special/jvmField.kt
new file mode 100644
index 00000000..9a8c9117
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/special/jvmField.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.special
+
+public open class JvmFieldsClass {
+ @JvmField
+ public var publicField = "x"
+
+ @JvmField
+ internal var internalField = "y"
+
+ @JvmField
+ protected var protectedField = "y"
+
+ public companion object JvmFieldsCompanion {
+ @JvmField
+ public var publicСField = "x"
+
+ @JvmField
+ internal var internalСField = "y"
+
+ @JvmField
+ protected var protectedСField = "y"
+
+ public const val publicConst = 1
+ internal const val internalConst = 2
+ protected const val protectedConst = 3
+ private const val privateConst = 4
+ }
+}
+
+public object JvmFieldsObject {
+ @JvmField
+ public var publicField = "x"
+
+ @JvmField
+ internal var internalField = "y"
+
+ public const val publicConst = 1
+ internal const val internalConst = 2
+ private const val privateConst = 4
+}
+
+
+@JvmField
+public var publicField = "x"
+
+@JvmField
+internal var internalField = "y"
+
+public const val publicConst = 1
+internal const val internalConst = 2
+private const val privateConst = 4
diff --git a/binary-compatibility-validator/test/cases/special/jvmNames.kt b/binary-compatibility-validator/test/cases/special/jvmNames.kt
new file mode 100644
index 00000000..e304de01
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/special/jvmNames.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.special
+
+@JvmName("internalFun")
+internal fun internalRenamedFun() {}
+
+internal var internalVar: Int = 1
+ @JvmName("internalVarGetter")
+ get
+ @JvmName("internalVarSetter")
+ set
+
+@JvmName("publicFun")
+public fun publicRenamedFun() {}
+
+public var publicVar: Int = 1
+ @JvmName("publicVarGetter")
+ get
+ @JvmName("publicVarSetter")
+ set
+
+
+
diff --git a/binary-compatibility-validator/test/cases/special/jvmOverloads.kt b/binary-compatibility-validator/test/cases/special/jvmOverloads.kt
new file mode 100644
index 00000000..8f238138
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/special/jvmOverloads.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UNUSED_PARAMETER")
+
+package cases.special
+
+
+@JvmOverloads
+public fun publicFunWithOverloads(a: Int = 0, b: String? = null) {}
+
+@JvmOverloads
+internal fun internalFunWithOverloads(a: Int = 0, b: String? = null) {}
+
+public class ClassWithOverloads
+ @JvmOverloads
+ internal constructor(val a: Int = 0, val b: String? = null) {
+
+ @JvmOverloads
+ internal fun internalFunWithOverloads(a: Int = 0, b: String? = null) {}
+
+} \ No newline at end of file
diff --git a/binary-compatibility-validator/test/cases/special/special.txt b/binary-compatibility-validator/test/cases/special/special.txt
new file mode 100644
index 00000000..40c074d2
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/special/special.txt
@@ -0,0 +1,61 @@
+public final class cases/special/ClassWithLateInitMembers {
+ public final fun getPublicLateInitWithInternalSet ()Ljava/lang/String;
+}
+
+public final class cases/special/ClassWithOverloads {
+ public final fun getA ()I
+ public final fun getB ()Ljava/lang/String;
+}
+
+public final class cases/special/HiddenClass {
+ public synthetic fun <init> ()V
+ public final synthetic fun getHiddenVal ()I
+ public final synthetic fun getHiddenVar ()I
+ public final synthetic fun getVarWithHiddenAccessors ()Ljava/lang/String;
+ public final synthetic fun hiddenFun ()V
+ public final synthetic fun setHiddenVar (I)V
+ public final synthetic fun setVarWithHiddenAccessors (Ljava/lang/String;)V
+}
+
+public final class cases/special/HiddenKt {
+ public static final synthetic fun hiddenTopLevelFun ()V
+}
+
+public final class cases/special/JvmFieldKt {
+ public static final field publicConst I
+ public static field publicField Ljava/lang/String;
+}
+
+public class cases/special/JvmFieldsClass {
+ public static final field JvmFieldsCompanion Lcases/special/JvmFieldsClass$JvmFieldsCompanion;
+ protected static final field protectedConst I
+ protected field protectedField Ljava/lang/String;
+ protected static field protectedСField Ljava/lang/String;
+ public static final field publicConst I
+ public field publicField Ljava/lang/String;
+ public static field publicСField Ljava/lang/String;
+ public fun <init> ()V
+}
+
+public final class cases/special/JvmFieldsClass$JvmFieldsCompanion {
+}
+
+public final class cases/special/JvmFieldsObject {
+ public static final field INSTANCE Lcases/special/JvmFieldsObject;
+ public static final field publicConst I
+ public static field publicField Ljava/lang/String;
+}
+
+public final class cases/special/JvmNamesKt {
+ public static final fun publicFun ()V
+ public static final fun publicVarGetter ()I
+ public static final fun publicVarSetter (I)V
+}
+
+public final class cases/special/JvmOverloadsKt {
+ public static final fun publicFunWithOverloads ()V
+ public static final fun publicFunWithOverloads (I)V
+ public static final fun publicFunWithOverloads (ILjava/lang/String;)V
+ public static synthetic fun publicFunWithOverloads$default (ILjava/lang/String;ILjava/lang/Object;)V
+}
+
diff --git a/binary-compatibility-validator/test/cases/whenMappings/enumWhen.kt b/binary-compatibility-validator/test/cases/whenMappings/enumWhen.kt
new file mode 100644
index 00000000..ff3a1d01
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/whenMappings/enumWhen.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.whenMappings
+
+enum class SampleEnum {
+ A,
+ B,
+ C
+}
+
+fun SampleEnum.deacronimize() = when (this) {
+ SampleEnum.A -> "Apple"
+ SampleEnum.B -> "Biscuit"
+ SampleEnum.C -> "Cinnamon"
+}
+
+
+inline fun SampleEnum.switch(thenA: () -> Unit, thenB: () -> Unit, thenC: () -> Unit) = when (this) {
+ SampleEnum.C -> thenC()
+ SampleEnum.B -> thenB()
+ SampleEnum.A -> thenA()
+}
diff --git a/binary-compatibility-validator/test/cases/whenMappings/sealedClassWhen.kt b/binary-compatibility-validator/test/cases/whenMappings/sealedClassWhen.kt
new file mode 100644
index 00000000..5cd80b5d
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/whenMappings/sealedClassWhen.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package cases.whenMappings
+
+sealed class SampleSealed {
+ class A : SampleSealed()
+ class B : SampleSealed()
+ class C : SampleSealed()
+}
+
+fun SampleSealed.deacronimize() = when (this) {
+ is SampleSealed.A -> "Apple"
+ is SampleSealed.B -> "Biscuit"
+ is SampleSealed.C -> "Cinnamon"
+}
+
+
+inline fun SampleSealed.switch(thenA: () -> Unit, thenB: () -> Unit, thenC: () -> Unit) = when (this) {
+ is SampleSealed.C -> thenC()
+ is SampleSealed.B -> thenB()
+ is SampleSealed.A -> thenA()
+}
diff --git a/binary-compatibility-validator/test/cases/whenMappings/whenMappings.txt b/binary-compatibility-validator/test/cases/whenMappings/whenMappings.txt
new file mode 100644
index 00000000..1975cf13
--- /dev/null
+++ b/binary-compatibility-validator/test/cases/whenMappings/whenMappings.txt
@@ -0,0 +1,33 @@
+public final class cases/whenMappings/EnumWhenKt {
+ public static final fun deacronimize (Lcases/whenMappings/SampleEnum;)Ljava/lang/String;
+ public static final fun switch (Lcases/whenMappings/SampleEnum;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
+}
+
+public final class cases/whenMappings/SampleEnum : java/lang/Enum {
+ public static final field A Lcases/whenMappings/SampleEnum;
+ public static final field B Lcases/whenMappings/SampleEnum;
+ public static final field C Lcases/whenMappings/SampleEnum;
+ public static fun valueOf (Ljava/lang/String;)Lcases/whenMappings/SampleEnum;
+ public static fun values ()[Lcases/whenMappings/SampleEnum;
+}
+
+public abstract class cases/whenMappings/SampleSealed {
+}
+
+public final class cases/whenMappings/SampleSealed$A : cases/whenMappings/SampleSealed {
+ public fun <init> ()V
+}
+
+public final class cases/whenMappings/SampleSealed$B : cases/whenMappings/SampleSealed {
+ public fun <init> ()V
+}
+
+public final class cases/whenMappings/SampleSealed$C : cases/whenMappings/SampleSealed {
+ public fun <init> ()V
+}
+
+public final class cases/whenMappings/SealedClassWhenKt {
+ public static final fun deacronimize (Lcases/whenMappings/SampleSealed;)Ljava/lang/String;
+ public static final fun switch (Lcases/whenMappings/SampleSealed;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
+}
+
diff --git a/binary-compatibility-validator/test/utils.kt b/binary-compatibility-validator/test/utils.kt
new file mode 100644
index 00000000..c7844108
--- /dev/null
+++ b/binary-compatibility-validator/test/utils.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.tools
+
+import java.io.*
+import kotlin.test.*
+
+private val OVERWRITE_EXPECTED_OUTPUT =
+ System.getProperty("overwrite.output")?.toBoolean() ?: false // use -Doverwrite.output=true
+
+fun List<ClassBinarySignature>.dumpAndCompareWith(to: File) {
+ if (!to.exists()) {
+ to.parentFile?.mkdirs()
+ to.bufferedWriter().use { dump(to = it) }
+ fail("Expected data file did not exist. Generated: $to")
+ } else {
+ val actual = dump(to = StringBuilder())
+ assertEqualsToFile(to, actual)
+ }
+}
+
+private fun assertEqualsToFile(to: File, actual: CharSequence) {
+ val actualText = actual.trimTrailingWhitespacesAndAddNewlineAtEOF()
+ val expectedText = to.readText().trimTrailingWhitespacesAndAddNewlineAtEOF()
+ if (expectedText == actualText) return // Ok
+ // Difference
+ if (OVERWRITE_EXPECTED_OUTPUT) {
+ to.writeText(actualText)
+ println("Generated: $to")
+ return // make test pass when overwriting output
+ }
+ // Fail on difference
+ assertEquals(
+ expectedText,
+ actualText,
+ "Actual data differs from file content: ${to.name}\nTo overwrite the expected API rerun with -Doverwrite.output=true parameter\n"
+ )
+}
+
+private fun CharSequence.trimTrailingWhitespacesAndAddNewlineAtEOF(): String =
+ this.lineSequence().map { it.trimEnd() }.joinToString(separator = "\n").let {
+ if (it.endsWith("\n")) it else it + "\n"
+ }
+
+
+private val UPPER_CASE_CHARS = Regex("[A-Z]+")
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..4fe2225e
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import org.jetbrains.kotlin.konan.target.HostManager
+
+apply from: rootProject.file("gradle/experimental.gradle")
+
+def rootModule = "kotlinx.coroutines"
+def coreModule = "kotlinx-coroutines-core"
+// Not applicable for Kotlin plugin
+def sourceless = ['kotlinx.coroutines', 'site', 'kotlinx-coroutines-bom']
+def internal = ['kotlinx.coroutines', 'site', 'benchmarks', 'knit', 'js-stub', 'stdlib-stubs', 'binary-compatibility-validator']
+// Not published
+def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js', 'android-unit-tests']
+
+static def platformOf(project) {
+ def name = project.name
+ if (name.endsWith("-js")) return "js"
+ if (name.endsWith("-common") || name.endsWith("-native")) {
+ throw IllegalStateException("$name platform is not supported")
+ }
+ return "jvm"
+}
+
+buildscript {
+ /*
+ * These property group is used to build kotlinx.coroutines against Kotlin compiler snapshot.
+ * How does it work:
+ * When build_snapshot_train is set to true, kotlin_version property is overridden with kotlin_snapshot_version,
+ * atomicfu_version is overwritten by TeamCity environment (AFU is built with snapshot and published to mavenLocal
+ * as previous step or the snapshot build).
+ * Additionally, mavenLocal and Sonatype snapshots are added to repository list and stress tests are disabled.
+ * DO NOT change the name of these properties without adapting kotlinx.train build chain.
+ */
+ def prop = rootProject.properties['build_snapshot_train']
+ ext.build_snapshot_train = prop != null && prop != ""
+ if (build_snapshot_train) {
+ ext.kotlin_version = rootProject.properties['kotlin_snapshot_version']
+ if (kotlin_version == null) {
+ throw new IllegalArgumentException("'kotlin_snapshot_version' should be defined when building with snapshot compiler")
+ }
+ }
+
+ if (build_snapshot_train || atomicfu_version.endsWith("-SNAPSHOT")) {
+ repositories {
+ mavenLocal()
+ maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
+ }
+ }
+
+ repositories {
+ jcenter()
+ maven { url "https://kotlin.bintray.com/kotlinx" }
+ maven { url "https://kotlin.bintray.com/kotlin-dev" }
+ maven { url "https://kotlin.bintray.com/kotlin-eap" }
+ maven { url "https://jetbrains.bintray.com/kotlin-native-dependencies" }
+ maven { url "https://plugins.gradle.org/m2/" }
+ }
+
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
+ classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
+ classpath "com.moowork.gradle:gradle-node-plugin:$gradle_node_version"
+ classpath "io.spring.gradle:dependency-management-plugin:$spring_dependency_management_version"
+
+ // JMH plugins
+ classpath "com.github.jengelman.gradle.plugins:shadow:4.0.2"
+ classpath "me.champeau.gradle:jmh-gradle-plugin:0.4.7"
+ classpath "net.ltgt.gradle:gradle-apt-plugin:0.10"
+ }
+}
+
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+
+// todo:KLUDGE: This is needed to workaround dependency resolution between Java and MPP modules
+def configureKotlinJvmPlatform(configuration) {
+ configuration.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm)
+}
+
+allprojects {
+ // the only place where HostManager could be instantiated
+ project.ext.hostManager = new HostManager()
+ def deployVersion = properties['DeployVersion']
+ if (deployVersion != null) version = deployVersion
+
+ if (build_snapshot_train) {
+ ext.kotlin_version = rootProject.properties['kotlin_snapshot_version']
+ println "Using Kotlin $kotlin_version for project $it"
+
+ def skipSnapshotChecks = rootProject.properties['skip_snapshot_checks'] != null
+ if (!skipSnapshotChecks && version != atomicfu_version) {
+ throw new IllegalStateException("Current deploy version is $version, but atomicfu version is not overridden ($atomicfu_version) for $it")
+ }
+
+ kotlin_version = rootProject.properties['kotlin_snapshot_version']
+ }
+
+ if (build_snapshot_train || atomicfu_version.endsWith("-SNAPSHOT")) {
+ repositories {
+ mavenLocal()
+ maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
+ }
+ }
+
+ ext.unpublished = unpublished
+
+ // This project property is set during nightly stress test
+ def stressTest = project.properties['stressTest']
+
+ // Copy it to all test tasks
+ tasks.withType(Test) {
+ systemProperty 'stressTest', stressTest
+ }
+}
+
+allprojects {
+ apply plugin: 'kotlinx-atomicfu' // it also adds all the necessary dependencies
+ def projectName = it.name
+ repositories {
+ /*
+ * google should be first in the repository list because some of the play services
+ * transitive dependencies was removed from jcenter, thus breaking gradle dependency resolution
+ */
+ if (projectName == "kotlinx-coroutines-play-services") {
+ google()
+ }
+ jcenter()
+ maven { url "https://kotlin.bintray.com/kotlin-dev" }
+ maven { url "https://kotlin.bintray.com/kotlin-eap" }
+ maven { url "https://kotlin.bintray.com/kotlinx" }
+ }
+
+ if (projectName == rootModule || projectName == coreModule) return
+
+ // Add dependency to core source sets. Core is configured in kx-core/build.gradle
+ evaluationDependsOn(":$coreModule")
+ if (sourceless.contains(projectName)) return
+
+ def platform = platformOf(it)
+ apply from: rootProject.file("gradle/compile-${platform}.gradle")
+
+ dependencies {
+ // See comment below for rationale, it will be replaced with "project" dependency
+ compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"
+
+ // the only way IDEA can resolve test classes
+ testCompile project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+ }
+
+ tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
+ kotlinOptions.freeCompilerArgs += experimentalAnnotations.collect { "-Xuse-experimental=" + it }
+ kotlinOptions.freeCompilerArgs += "-progressive"
+ kotlinOptions.freeCompilerArgs += "-XXLanguage:+InlineClasses"
+ // Binary compatibility support
+ kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"]
+ }
+}
+
+if (build_snapshot_train) {
+ println "Hacking test tasks, removing stress and flaky tests"
+ allprojects {
+ tasks.withType(Test).all {
+ exclude '**/*LinearizabilityTest*'
+ exclude '**/*LFTest*'
+ exclude '**/*StressTest*'
+ exclude '**/*scheduling*'
+ exclude '**/*Timeout*'
+ exclude '**/*definitely/not/kotlinx*'
+ }
+ }
+
+ println "Manifest of kotlin-compiler-embeddable.jar for coroutines"
+ configure(subprojects.findAll { it.name == "kotlinx-coroutines-core" }) {
+ configurations.matching { it.name == "kotlinCompilerClasspath" }.all {
+ resolvedConfiguration.getFiles().findAll { it.name.contains("kotlin-compiler-embeddable") }.each {
+ def manifest = zipTree(it).matching {
+ include 'META-INF/MANIFEST.MF'
+ }.getFiles().first()
+
+ manifest.readLines().each {
+ println it
+ }
+ }
+ }
+ }
+}
+
+/*
+ * 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) && it.name != "benchmarks" && it.name != coreModule }) {
+ sourceSets {
+ main.kotlin.srcDirs = ['src']
+ test.kotlin.srcDirs = ['test']
+ main.resources.srcDirs = ['resources']
+ test.resources.srcDirs = ['test-resources']
+ }
+}
+
+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"
+
+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/publish-bintray.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 URL("file://$core_docs_file")
+ }
+ }
+ }
+ }
+}
+
+// Report Kotlin compiler version when building project
+println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION")
+
+// --------------- Configure sub-projects that are published ---------------
+task deploy(dependsOn: getTasksByName("publish", true) + getTasksByName("publishNpm", true))
+
+apply plugin: 'base'
+
+clean.dependsOn gradle.includedBuilds.collect { it.task(':clean') }
diff --git a/bump-version.sh b/bump-version.sh
new file mode 100755
index 00000000..00930cbd
--- /dev/null
+++ b/bump-version.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+if [ "$#" -ne 2 ]
+ then
+ echo "Use: ./bump-version old_version new_version"
+ exit
+fi
+
+old_version=$1
+new_version=$2
+
+update_version() {
+ echo "Updating version from '$old_version' to '$new_version' in $1"
+ sed -i.bak s/$old_version/$new_version/g $1
+ rm $1.bak
+}
+
+update_version "README.md"
+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
+escaped_old_version=$(echo $old_version | sed s/[.]/\\\\./g)
+result=$(find ./ -type f \( -iname \*.properties -o -iname \*.md \) | grep -v "\.gradle" | grep -v "build" | xargs -I{} grep -H "$escaped_old_version" {} | grep -v CHANGES.md | grep -v COMPATIBILITY.md)
+if [ -z "$result" ];
+then
+ echo "Done"
+else
+ echo "ERROR: Previous version is present in the project: $result"
+ exit -1
+fi
diff --git a/coroutines-guide.md b/coroutines-guide.md
new file mode 100644
index 00000000..4a7fb67a
--- /dev/null
+++ b/coroutines-guide.md
@@ -0,0 +1,120 @@
+The main coroutines guide has moved to the [docs folder](docs/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)
+<!--- 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='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'></a>[Flow cancellation](docs/flow.md#flow-cancellation)
+ * <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='upstream-exceptions-only'></a>[Upstream exceptions only](docs/flow.md#upstream-exceptions-only)
+ * <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)
+<!--- 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)
+<!--- END -->
diff --git a/docs/_nav.yml b/docs/_nav.yml
new file mode 100644
index 00000000..79a69690
--- /dev/null
+++ b/docs/_nav.yml
@@ -0,0 +1,30 @@
+- 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
new file mode 100644
index 00000000..6a1248b5
--- /dev/null
+++ b/docs/basics.md
@@ -0,0 +1,416 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class 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_TOC -->
+
+
+## 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 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 replacing
+`GlobalScope.launch { ... }` with `thread { ... }` and `delay(...)` with `Thread.sleep(...)`. Try it.
+
+If you start by replacing `GlobalScope.launch` by `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_
+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 [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 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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-03.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 `main` function that is turned into a coroutine using [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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.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
+[coroutineScope] builder. It creates a coroutine scope and does not complete until all launched children
+complete. The main difference between [runBlocking] and [coroutineScope] is that the latter does not block the current thread
+while waiting for all children to complete.
+
+<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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt).
+
+<!--- TEST
+Task from coroutine scope
+Task from runBlocking
+Task from nested launch
+Coroutine scope is over
+-->
+
+### 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 `suspend` modifier.
+That 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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt).
+
+<!--- TEST
+Hello,
+World!
+-->
+
+
+But what if the extracted function contains a coroutine builder which is invoked on the current scope?
+In this case `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 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 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(1000L)
+ print(".")
+ }
+ }
+}
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
+
+<!--- TEST lines.size == 1 && lines[0] == ".".repeat(100_000) -->
+
+It launches 100K coroutines and, after a second, 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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-07.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 -->
+
+
diff --git a/docs/cancellation-and-timeouts.md b/docs/cancellation-and-timeouts.md
new file mode 100644
index 00000000..ef4a9c9e
--- /dev/null
+++ b/docs/cancellation-and-timeouts.md
@@ -0,0 +1,388 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/CancellationTimeOutsGuideTest.kt
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class CancellationTimeOutsGuideTest {
+-->
+**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)
+
+<!--- END_TOC -->
+
+## 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 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 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 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 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 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 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 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 -->
+
+<!--- 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 -->
diff --git a/docs/channels.md b/docs/channels.md
new file mode 100644
index 00000000..55507597
--- /dev/null
+++ b/docs/channels.md
@@ -0,0 +1,715 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/ChannelsGuideTest.kt
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class ChannelsGuideTest {
+-->
+**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_TOC -->
+
+## 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 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 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 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
+ for (i in 1..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 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)
+ for (i in 1..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 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 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 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 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 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") // initial delay hasn't passed yet
+
+ nextElement = withTimeoutOrNull(50) { tickerChannel.receive() } // all subsequent elements has 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 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 -->
diff --git a/docs/compatibility.md b/docs/compatibility.md
new file mode 100644
index 00000000..e56fc1be
--- /dev/null
+++ b/docs/compatibility.md
@@ -0,0 +1,131 @@
+<!---
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+--->
+
+<!--- 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_TOC -->
+
+## 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 -->
diff --git a/docs/composing-suspending-functions.md b/docs/composing-suspending-functions.md
new file mode 100644
index 00000000..0cd02762
--- /dev/null
+++ b/docs/composing-suspending-functions.md
@@ -0,0 +1,451 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/ComposingGuideTest.kt
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class ComposingGuideTest {
+-->
+
+**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_TOC -->
+
+## 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 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 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 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 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] 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 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 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 -->
diff --git a/docs/coroutine-context-and-dispatchers.md b/docs/coroutine-context-and-dispatchers.md
new file mode 100644
index 00000000..558b0397
--- /dev/null
+++ b/docs/coroutine-context-and-dispatchers.md
@@ -0,0 +1,712 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class DispatchersGuideTest {
+-->
+
+**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)
+ * [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_TOC -->
+
+## 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 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 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. The common approach to debugging applications with
+threads 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 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 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 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 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 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 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 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>
+
+Alternatively, we can implement the [CoroutineScope] interface in this `Activity` class. The best way to do it is
+to use delegation with default factory functions.
+We also can combine the desired dispatcher (we used [Dispatchers.Default] in this example) with the scope:
+
+<div class="sample" markdown="1" theme="idea" data-highlight-only>
+
+```kotlin
+ class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
+ // to be continued ...
+```
+
+</div>
+
+Now, we can launch coroutines in the scope of this `Activity` without having to explicitly
+specify their context. 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 ->
+ 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 kotlin.coroutines.*
+import kotlinx.coroutines.*
+
+class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
+
+ fun destroy() {
+ cancel() // Extension on CoroutineScope
+ }
+ // to be continued ...
+
+ // class Activity continues
+ fun doSomething() {
+ // launch ten coroutines for a demo, each working for a different time
+ repeat(10) { i ->
+ 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 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()`.
+
+### 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 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 on what thread the coroutine is executed.
+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 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 -->
diff --git a/docs/coroutines-guide.md b/docs/coroutines-guide.md
new file mode 100644
index 00000000..5f41d060
--- /dev/null
+++ b/docs/coroutines-guide.md
@@ -0,0 +1,33 @@
+
+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 `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)
+* [Guide to reactive streams with coroutines](../reactive/coroutines-guide-reactive.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)
diff --git a/docs/debugging.md b/docs/debugging.md
new file mode 100644
index 00000000..e2c7ec1e
--- /dev/null
+++ b/docs/debugging.md
@@ -0,0 +1,97 @@
+**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)
+
+<!--- END_TOC -->
+
+
+## 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)
+-->
+
+<!--- 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/exception-handling.md b/docs/exception-handling.md
new file mode 100644
index 00000000..409acd53
--- /dev/null
+++ b/docs/exception-handling.md
@@ -0,0 +1,531 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/ExceptionsGuideTest.kt
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class ExceptionsGuideTest {
+-->
+**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_TOC -->
+
+## Exception Handling
+
+
+This section covers exception handling and cancellation on exceptions.
+We already know that cancelled coroutine throws [CancellationException] in suspension points and that it
+is ignored by coroutines machinery. But 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]).
+The former treat exceptions as unhandled, 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 coroutines in the [GlobalScope]:
+
+<div class="sample" markdown="1" theme="idea" data-highlight-only>
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ val job = GlobalScope.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 {
+ 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 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
+
+But what if one does not want to print all exceptions to the console?
+[CoroutineExceptionHandler] context element is used as generic `catch` block of coroutine where custom logging or exception handling may take place.
+It is similar to using [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
+
+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 exceptions which are not expected to be handled by the user,
+so registering it in [async] builder and the like of it has no effect.
+
+<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("Caught $exception")
+ }
+ val job = GlobalScope.launch(handler) {
+ throw AssertionError()
+ }
+ val deferred = GlobalScope.async(handler) {
+ throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
+ }
+ joinAll(job, deferred)
+//sampleEnd
+}
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt).
+
+The output of this code is:
+
+```text
+Caught java.lang.AssertionError
+```
+
+<!--- TEST-->
+
+### Cancellation and exceptions
+
+Cancellation is tightly bound with 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] without a cause, it terminates, but it does not cancel its parent.
+Cancelling without cause is a mechanism for parent to cancel its children without cancelling itself.
+
+<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 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 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) which do not depend on
+[CoroutineExceptionHandler] implementation.
+The original exception is handled by the parent when all its children terminate.
+
+> This also a reason why, 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.
+
+<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("Caught $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 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
+Caught java.lang.ArithmeticException
+```
+<!--- TEST-->
+
+### Exceptions aggregation
+
+What happens if multiple children of a coroutine throw an exception?
+The general rule is "the first exception wins", so the first thrown exception is exposed to the handler.
+But that may cause lost exceptions, for example if coroutine throws an exception in its `finally` block.
+So, additional exceptions are suppressed.
+
+> One of the solutions would have been to report each exception separately,
+but then [Deferred.await] should have had the same mechanism to avoid behavioural inconsistency and this
+would cause implementation details of a coroutines (whether it had delegated parts of its work to its children or not)
+to leak to its exception handler.
+
+
+<!--- 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("Caught $exception with suppressed ${exception.suppressed.contentToString()}")
+ }
+ val job = GlobalScope.launch(handler) {
+ launch {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw ArithmeticException()
+ }
+ }
+ launch {
+ delay(100)
+ throw IOException()
+ }
+ delay(Long.MAX_VALUE)
+ }
+ job.join()
+}
+```
+
+</div>
+
+> You can get 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
+Caught java.io.IOException with suppressed [java.lang.ArithmeticException]
+```
+
+<!--- TEST-->
+
+> Note, this mechanism currently works only on Java version 1.7+.
+Limitation on JS and Native is temporary and will be fixed in the future.
+
+Cancellation exceptions are transparent and 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("Caught original $exception")
+ }
+ val job = GlobalScope.launch(handler) {
+ val inner = launch {
+ launch {
+ launch {
+ throw IOException()
+ }
+ }
+ }
+ try {
+ inner.join()
+ } catch (e: CancellationException) {
+ println("Rethrowing CancellationException with original cause")
+ throw e
+ }
+ }
+ job.join()
+//sampleEnd
+}
+```
+
+</div>
+
+> You can get 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
+Caught original java.io.IOException
+```
+<!--- TEST-->
+
+### Supervision
+
+As we have studied before, cancellation is a bidirectional relationship propagating through the whole
+coroutines hierarchy. But what if 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 required.
+
+Another example is a server process that spawns several children jobs and needs to _supervise_
+their execution, tracking their failures and restarting just those children jobs that had failed.
+
+#### Supervision job
+
+For these purposes [SupervisorJob][SupervisorJob()] can be used. It is similar to a regular [Job][Job()] with the only exception that cancellation is propagated
+only downwards. It is easy to demonstrate with an 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("First child is failing")
+ throw AssertionError("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("First child is cancelled: ${firstChild.isCancelled}, but second one is still active")
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ // But cancellation of the supervisor is propagated
+ println("Second child is cancelled because supervisor is cancelled")
+ }
+ }
+ // wait until the first child fails & completes
+ firstChild.join()
+ println("Cancelling supervisor")
+ supervisor.cancel()
+ secondChild.join()
+ }
+}
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt).
+
+The output of this code is:
+
+```text
+First child is failing
+First child is cancelled: true, but second one is still active
+Cancelling supervisor
+Second child is cancelled because supervisor is cancelled
+```
+<!--- TEST-->
+
+
+#### Supervision scope
+
+For *scoped* concurrency [supervisorScope] can be used instead of [coroutineScope] for the same purpose. It propagates cancellation
+only in one direction and cancels all children only if it has failed itself. It also waits for all children before completion
+just like [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("Child is sleeping")
+ delay(Long.MAX_VALUE)
+ } finally {
+ println("Child is cancelled")
+ }
+ }
+ // Give our child a chance to execute and print using yield
+ yield()
+ println("Throwing exception from scope")
+ throw AssertionError()
+ }
+ } catch(e: AssertionError) {
+ println("Caught assertion error")
+ }
+}
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt).
+
+The output of this code is:
+
+```text
+Child is sleeping
+Throwing exception from scope
+Child is cancelled
+Caught 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 exception handling mechanisms.
+This difference comes from the fact that child's failure is not propagated to the parent.
+
+<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("Caught $exception")
+ }
+ supervisorScope {
+ val child = launch(handler) {
+ println("Child throws an exception")
+ throw AssertionError()
+ }
+ println("Scope is completing")
+ }
+ println("Scope is completed")
+}
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt).
+
+The output of this code is:
+
+```text
+Scope is completing
+Child throws an exception
+Caught java.lang.AssertionError
+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.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
+[supervisorScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
+[coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-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/flow.md b/docs/flow.md
new file mode 100644
index 00000000..43467da4
--- /dev/null
+++ b/docs/flow.md
@@ -0,0 +1,1855 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*-##\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/FlowGuideTest.kt
+// This file was automatically generated from flow.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class FlowGuideTest {
+-->
+
+**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](#flow-cancellation)
+ * [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)
+ * [Upstream exceptions only](#upstream-exceptions-only)
+ * [Imperative versus declarative](#imperative-versus-declarative)
+ * [Launching flow](#launching-flow)
+
+<!--- END_TOC -->
+
+## Asynchronous Flow
+
+Suspending functions asynchronously return a single value, but how can you return
+multiple asynchronously computed values? That is what Kotlin Flows are for.
+
+### Representing multiple values
+
+Multiple values can be represented in Kotlin using [collections].
+For example, we can have a function `foo()` that returns a [List]
+of three numbers and print them all using [forEach]:
+
+<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
+
+```kotlin
+fun foo(): List<Int> = listOf(1, 2, 3)
+
+fun main() {
+ foo().forEach { value -> println(value) }
+}
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt).
+
+This code outputs:
+
+```text
+1
+2
+3
+```
+
+<!--- TEST -->
+
+#### Sequences
+
+If the numbers are computed 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 foo(): 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() {
+ foo().forEach { value -> println(value) }
+}
+```
+
+</div>
+
+> You can get full code [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 those values are computed by an asynchronous code we can mark function `foo` 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 foo(): List<Int> {
+ delay(1000) // pretend we are doing something asynchronous here
+ return listOf(1, 2, 3)
+}
+
+fun main() = runBlocking<Unit> {
+ foo().forEach { value -> println(value) }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [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 `List<Int>` result type we can only return all the values at once. To represent
+the stream of values that are being asynchronously computed we can use [`Flow<Int>`][Flow] type similarly
+to 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 foo(): 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 see that the main thread is not blocked
+ launch {
+ for (k in 1..3) {
+ println("I'm not blocked $k")
+ delay(100)
+ }
+ }
+ // Collect the flow
+ foo().collect { value -> println(value) }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [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 of the code with the [Flow] from the earlier examples:
+
+* A builder function for [Flow] type is called [flow].
+* Code inside the `flow { ... }` builder block can suspend.
+* The function `foo()` 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.
+
+> You can replace [delay] with `Thread.sleep` in the body of `foo`'s `flow { ... }` and see that the main
+thread is blocked in this case.
+
+### Flows are cold
+
+Flows are _cold_ streams similarly to sequences &mdash; the code inside a [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 foo(): Flow<Int> = flow {
+ println("Flow started")
+ for (i in 1..3) {
+ delay(100)
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ println("Calling foo...")
+ val flow = foo()
+ println("Calling collect...")
+ flow.collect { value -> println(value) }
+ println("Calling collect again...")
+ flow.collect { value -> println(value) }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt).
+
+Which prints:
+
+```text
+Calling foo...
+Calling collect...
+Flow started
+1
+2
+3
+Calling collect again...
+Flow started
+1
+2
+3
+```
+
+<!--- TEST -->
+
+That is a key reason why the `foo()` function (which returns a flow) is not marked with `suspend` modifier.
+By itself, `foo()` returns quickly and does not wait for anything. The flow starts every time it is collected,
+that is why we see that when we call `collect` again, we get "Flow started" printed again.
+
+### Flow cancellation
+
+Flow adheres to general cooperative cancellation of coroutines. However, flow infrastructure does not introduce
+additional cancellation points. It is fully transparent for cancellation. As usual, flow collection can be
+cancelled when the flow is suspended in a cancellable suspending function (like [delay]) and cannot be cancelled otherwise.
+
+The following example shows how the flow gets cancelled on timeout when running in [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 foo(): Flow<Int> = flow {
+ for (i in 1..3) {
+ delay(100)
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ withTimeoutOrNull(250) { // Timeout after 250ms
+ foo().collect { value -> println(value) }
+ }
+ println("Done")
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt).
+
+Notice how only two numbers get emitted by the flow in `foo()` function, producing the following output:
+
+```text
+Emitting 1
+1
+Emitting 2
+2
+Done
+```
+
+<!--- TEST -->
+
+### Flow builders
+
+The `flow { ... }` builder from the previous examples is the most basic one. There are other builders for
+convenient 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.
+
+Thus, the example that prints 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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt).
+
+<!--- TEST
+1
+2
+3
+-->
+
+### Intermediate flow operators
+
+Flows can be transformed with operators similarly to 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 from sequences is that blocks of
+code inside those operators can call suspending functions.
+
+For example, a flow of incoming requests can be
+mapped to 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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-08.kt).
+
+It produces the following three lines, each line appearing after a 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 `transform` operator, you 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 full code [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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt).
+
+The output of this code clearly shows that execution of the `flow { ... }` body in `numbers()` function
+had 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 for
+convenience:
+
+* 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 full code [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 intermediate operators from
+upstream to downstream and is delivered to the terminal operator after that.
+
+See the following example that filters 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 full code [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 `foo` flow, then the following code runs in the context specified
+by the author of this code, regardless of implementation details of the `foo` flow:
+
+<div class="sample" markdown="1" theme="idea" data-highlight-only>
+
+```kotlin
+withContext(context) {
+ foo.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 `foo` 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 foo(): Flow<Int> = flow {
+ log("Started foo flow")
+ for (i in 1..3) {
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ foo().collect { value -> log("Collected $value") }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt).
+
+Running this code produces:
+
+```text
+[main @coroutine#1] Started foo flow
+[main @coroutine#1] Collected 1
+[main @coroutine#1] Collected 2
+[main @coroutine#1] Collected 3
+```
+
+<!--- TEST FLEXIBLE_THREAD -->
+
+Since `foo().collect` is called from the main thread, the body of `foo`'s flow is also called in the main thread.
+This is a 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 code using Kotlin coroutines, but code in the `flow { ... }` builder has to honor 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 foo(): Flow<Int> = flow {
+ // 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> {
+ foo().collect { value -> println(value) }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt).
+
+This code produces the following exception:
+
+<!--- TEST EXCEPTION
+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, DefaultDispatcher].
+ Please refer to 'flow' documentation or use 'flowOn' instead
+ at ...
+-->
+
+> Note that we had to use a fully qualified name of [kotlinx.coroutines.withContext][withContext] function in this example to
+demonstrate this exception. A short name of `withContext` would have resolved to a special stub function that
+produces compilation error to prevent us from running into this problem.
+
+#### flowOn operator
+
+The exception refers to [flowOn] function that shall be used to change the context of flow emission.
+The correct way of changing the context of a flow is shown in the below example, which also prints
+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 foo(): 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> {
+ foo().collect { value ->
+ log("Collected $value")
+ }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [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 observation here is that [flowOn] operator had 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 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 overall time it takes
+to collect the flow, especially when long-running asynchronous operations are involved. For example, consider a case when
+emission by `foo()` flow is slow, taking 100 ms to produce an element; and collector is also slow,
+taking 300 ms to process an element. Let us see how long does it take 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 foo(): 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 {
+ foo().collect { value ->
+ delay(300) // pretend we are processing it for 300 ms
+ println(value)
+ }
+ }
+ println("Collected in $time ms")
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt).
+
+It produces something like this, the whole collection taking around 1200 ms (three numbers times 400 ms each):
+
+```text
+1
+2
+3
+Collected in 1220 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+We can use [buffer] operator on a flow to run emitting code of `foo()` 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 foo(): 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 {
+ foo()
+ .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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt).
+
+It produces the same numbers faster, as we have effectively created a processing pipeline,
+only having to 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 [flowOn] operator uses the same buffering mechanism when it has to change [CoroutineDispatcher],
+but here we explicitly request buffering without changing execution context.
+
+#### Conflation
+
+When flow represents partial results of some operation or operation status updates, it may not be necessary
+to process each value, but only to process the most recent ones. In this case, [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 foo(): 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 {
+ foo()
+ .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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt).
+
+We see that while the first number was being processed the second and the third ones 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 emitter and collector are slow. It does that by dropping emitted values.
+The other way is to cancel 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 `xxx` operator, but cancel the
+code in their block on a new value. Let us change the previous example from [conflate] to [collectLatest]:
+
+<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 foo(): 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 {
+ foo()
+ .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 full code [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 several ways to compose multiple flows.
+
+#### Zip
+
+Similarly to [Sequence.zip] extension function in the Kotlin standard library,
+flows have [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 full code [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 some variable or operation (see also a 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 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 [zip] operator would still produce the same result,
+albeit results are going to be printed every 400 ms:
+
+> We use [onEach] intermediate operator in this example to delay each element and thus 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 full code [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, using [combine] operator here instead of [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 full code [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 purpose. However, the asynchronous nature of flows calls for different _modes_ of flattening
+thus 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 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 full code [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 full code [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 [flatMapMerge] call its block of code (`{ requestFlow(it) }` in this example) sequentially, but
+collects the resulting flows concurrently, so it is equivalent to performing a sequential
+`map { requestFlow(it) }` first and then calling [flattenMerge] on the result.
+
+#### flatMapLatest
+
+In a similar way to [collectLatest] operator that was shown in
+["Processing the latest value"](#processing-the-latest-value) section, there is the corresponding "Latest"
+flattening mode where collection of the previous flow is cancelled as soon as new flow is emitted.
+It is implemented by [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 full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt).
+
+The output of this example speaks for the way [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 emitter or any code inside any of 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 foo(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i) // emit next value
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ try {
+ foo().collect { value ->
+ println(value)
+ check(value <= 1) { "Collected $value" }
+ }
+ } catch (e: Throwable) {
+ println("Caught $e")
+ }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt).
+
+This code successfully catches an exception in [collect] terminal operator and,
+as you can 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 emitter or in any intermediate or terminal operators.
+For example, let us 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 foo(): 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 {
+ foo().collect { value -> println(value) }
+ } catch (e: Throwable) {
+ println("Caught $e")
+ }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [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 emitter encapsulate its exception handling behavior?
+
+Flows must be _transparent to exceptions_ and it is a violation of exception transparency to [emit][FlowCollector.emit] values in the
+`flow { ... }` builder from inside of `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 [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 a 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 foo(): 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
+ foo()
+ .catch { e -> emit("Caught $e") } // emit on exception
+ .collect { value -> println(value) }
+//sampleEnd
+}
+```
+
+</div>
+
+> You can get full code [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 foo(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ foo()
+ .catch { e -> println("Caught $e") } // does not catch downstream exceptions
+ .collect { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt).
+
+The "Caught ..." message is not printed despite the `catch` operator:
+
+<!--- TEST EXCEPTION
+Emitting 1
+1
+Emitting 2
+Exception in thread "main" java.lang.IllegalStateException: Collected 2
+ at ...
+-->
+
+#### Catching declaratively
+
+We can combine a declarative nature of [catch] operator with a desire to handle all exceptions by moving the body
+of [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 foo(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ foo()
+ .onEach { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+ .catch { e -> println("Caught $e") }
+ .collect()
+//sampleEnd
+}
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt).
+
+Now we can see that "Caught ..." message is printed and thus we can catch all exceptions without explicitly
+using a `try/catch` block:
+
+<!--- TEST EXCEPTION
+Emitting 1
+1
+Emitting 2
+Caught java.lang.IllegalStateException: Collected 2
+-->
+
+### Flow completion
+
+When flow collection completes (normally or exceptionally) it may be needed to execute some action.
+As you might have already noticed, it also can be done in two ways: imperative and declarative.
+
+#### Imperative finally block
+
+In addition to `try`/`catch`, a collector can also use `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 foo(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+ try {
+ foo().collect { value -> println(value) }
+ } finally {
+ println("Done")
+ }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt).
+
+This code prints three numbers produced by the `foo()` flow followed by "Done" string:
+
+```text
+1
+2
+3
+Done
+```
+
+<!--- TEST -->
+
+#### Declarative handling
+
+For declarative approach, flow has [onCompletion] intermediate operator that is invoked
+when the flow is completely collected.
+
+The previous example can be rewritten using [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 foo(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ foo()
+ .onCompletion { println("Done") }
+ .collect { value -> println(value) }
+//sampleEnd
+}
+```
+</div>
+
+> You can get full code [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 flow collection was completed normally or exceptionally. In the following
+example `foo()` flow throws exception after emitting 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 foo(): Flow<Int> = flow {
+ emit(1)
+ throw RuntimeException()
+}
+
+fun main() = runBlocking<Unit> {
+ foo()
+ .onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
+ .catch { cause -> println("Caught exception") }
+ .collect { value -> println(value) }
+}
+//sampleEnd
+```
+</div>
+
+> You can get full code [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 -->
+
+[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 `catch` operator.
+
+#### Upstream exceptions only
+
+Just like [catch] operator, [onCompletion] sees only exception coming from upstream and does not
+see downstream exceptions. For example, run 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 foo(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+ foo()
+ .onCompletion { cause -> println("Flow completed with $cause") }
+ .collect { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt).
+
+And you can see the completion cause is null, yet collection failed with exception:
+
+```text
+1
+Flow completed with null
+Exception in thread "main" java.lang.IllegalStateException: Collected 2
+```
+
+<!--- TEST EXCEPTION -->
+
+### Imperative versus declarative
+
+Now we know how to collect flow, handle its completion and exceptions in both imperative and declarative ways.
+The natural question here is which approach should be 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 convenient to use flows to represent asynchronous events that are coming from some source.
+In this case, we need an analogue of `addEventListener` function that registers a piece of code with a reaction
+on 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 [collect] terminal operator after `onEach`, then code after it waits 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 full code [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 -->
+
+Here [launchIn] terminal operator comes in handy. Replacing `collect` with `launchIn` we can
+launch 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 full code [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 [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 real applications a scope is going to come from some entity with a limited
+lifetime. As soon as the lifetime of this entity is terminated the corresponding scope is cancelled, cancelling
+collection of the corresponding flow. This way the pair of `onEach { ... }.launchIn(scope)` works
+like `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.
+
+<!-- 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
+<!--- 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
+<!--- END -->
diff --git a/docs/images/after.png b/docs/images/after.png
new file mode 100644
index 00000000..4ce15e8b
--- /dev/null
+++ b/docs/images/after.png
Binary files differ
diff --git a/docs/images/before.png b/docs/images/before.png
new file mode 100644
index 00000000..31b91060
--- /dev/null
+++ b/docs/images/before.png
Binary files differ
diff --git a/docs/select-expression.md b/docs/select-expression.md
new file mode 100644
index 00000000..f36fa09b
--- /dev/null
+++ b/docs/select-expression.md
@@ -0,0 +1,568 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class SelectGuideTest {
+-->
+
+
+**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_TOC -->
+
+
+
+## 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 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 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 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 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 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 -->
diff --git a/docs/shared-mutable-state-and-concurrency.md b/docs/shared-mutable-state-and-concurrency.md
new file mode 100644
index 00000000..30d7334e
--- /dev/null
+++ b/docs/shared-mutable-state-and-concurrency.md
@@ -0,0 +1,554 @@
+<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.$$1$$2
+-->
+<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
+<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class SharedStateGuideTest {
+-->
+**Table of contents**
+
+<!--- 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_TOC -->
+
+## 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 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 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 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 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 necessarily 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
+var counter = AtomicInteger()
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ counter.incrementAndGet()
+ }
+ }
+ println("Counter = $counter")
+}
+//sampleEnd
+```
+
+</div>
+
+> You can get 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 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 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 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 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 -->
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..335c9999
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,33 @@
+#
+# Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
+# Kotlin
+version=1.3.1-SNAPSHOT
+group=org.jetbrains.kotlinx
+kotlin_version=1.3.50
+
+# Dependencies
+junit_version=4.12
+atomicfu_version=0.13.0
+html_version=0.6.8
+lincheck_version=2.0
+dokka_version=0.9.16-rdev-2-mpp-hacks
+byte_buddy_version=1.9.3
+reactor_vesion=3.2.5.RELEASE
+reactive_streams_version=1.0.2
+rxjava2_version=2.2.8
+
+# JS
+gradle_node_version=1.2.0
+node_version=8.9.3
+npm_version=5.7.1
+mocha_version=4.1.0
+mocha_headless_chrome_version=1.8.2
+mocha_teamcity_reporter_version=2.2.2
+source_map_support_version=0.5.3
+spring_dependency_management_version=1.0.8.RELEASE
+
+# Settings
+kotlin.incremental.multiplatform=true
+kotlin.native.ignoreDisabledTargets=true
diff --git a/gradle/compile-common.gradle b/gradle/compile-common.gradle
new file mode 100644
index 00000000..ebad56a3
--- /dev/null
+++ b/gradle/compile-common.gradle
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+kotlin.sourceSets {
+ commonMain.dependencies {
+ api "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
+ }
+
+ commonTest.dependencies {
+ api "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version"
+ api "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version"
+ }
+}
diff --git a/gradle/compile-js-multiplatform.gradle b/gradle/compile-js-multiplatform.gradle
new file mode 100644
index 00000000..a9a4ea29
--- /dev/null
+++ b/gradle/compile-js-multiplatform.gradle
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2019 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 {
+ targets {
+ fromPreset(presets.js, 'js')
+ }
+
+ sourceSets {
+ jsMain.dependencies {
+ api "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
+ }
+
+ jsTest.dependencies {
+ api "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version"
+ }
+ }
+}
+
+// When source sets are configured
+apply from: rootProject.file('gradle/test-mocha-js.gradle')
+
+compileKotlinJs {
+ kotlinOptions.metaInfo = true
+ kotlinOptions.sourceMap = true
+ kotlinOptions.moduleKind = 'umd'
+
+ kotlinOptions {
+ // drop -js suffix from outputFile
+ def baseName = project.name - "-js"
+ outputFile = new File(outputFile.parent, baseName + ".js")
+ }
+}
+
+compileTestKotlinJs {
+ kotlinOptions.metaInfo = true
+ kotlinOptions.sourceMap = true
+ kotlinOptions.moduleKind = 'umd'
+}
+
+task populateNodeModules(type: Copy, dependsOn: compileTestKotlinJs) {
+ // we must copy output that is transformed by atomicfu
+ from(kotlin.targets.js.compilations.main.output.allOutputs)
+ into "$node.nodeModulesDir/node_modules"
+
+ def configuration = configurations.jsTestRuntimeClasspath
+ from(files {
+ configuration.collect { File file ->
+ file.name.endsWith(".jar") ?
+ zipTree(file.absolutePath).matching {
+ include '*.js'
+ include '*.js.map'
+ } : files()
+ }
+ }.builtBy(configuration))
+}
+
+npmInstall.dependsOn populateNodeModules
diff --git a/gradle/compile-js.gradle b/gradle/compile-js.gradle
new file mode 100644
index 00000000..4b1e3bde
--- /dev/null
+++ b/gradle/compile-js.gradle
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2019 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: 'kotlin2js'
+
+dependencies {
+ compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
+ testCompile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version"
+}
+
+tasks.withType(compileKotlin2Js.getClass()) {
+ kotlinOptions {
+ moduleKind = "umd"
+ sourceMap = true
+ metaInfo = true
+ }
+}
+
+compileKotlin2Js {
+ kotlinOptions {
+ // drop -js suffix from outputFile
+ def baseName = project.name - "-js"
+ outputFile = new File(outputFile.parent, baseName + ".js")
+ }
+}
diff --git a/gradle/compile-jvm-multiplatform.gradle b/gradle/compile-jvm-multiplatform.gradle
new file mode 100644
index 00000000..f6d76fca
--- /dev/null
+++ b/gradle/compile-jvm-multiplatform.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+sourceCompatibility = 1.6
+targetCompatibility = 1.6
+
+repositories {
+ maven { url "https://dl.bintray.com/devexperts/Maven/" }
+}
+
+kotlin {
+ targets {
+ fromPreset(presets.jvm, 'jvm')
+ }
+ sourceSets {
+ jvmMain.dependencies {
+ api 'org.jetbrains.kotlin:kotlin-stdlib'
+ }
+
+ jvmTest.dependencies {
+ api "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
+ // Workaround to make addSuppressed work in tests
+ api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ api "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
+ api "junit:junit:$junit_version"
+ }
+ }
+}
+
+jvmTest {
+ testLogging {
+ showStandardStreams = true
+ events "passed", "failed"
+ }
+ def stressTest = project.properties['stressTest']
+ if (stressTest != null) systemProperties['stressTest'] = stressTest
+}
diff --git a/gradle/compile-jvm.gradle b/gradle/compile-jvm.gradle
new file mode 100644
index 00000000..3ab25456
--- /dev/null
+++ b/gradle/compile-jvm.gradle
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-2019 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: 'kotlin'
+
+sourceCompatibility = 1.6
+targetCompatibility = 1.6
+
+dependencies {
+ compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ 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"
+}
+
+repositories {
+ maven { url "https://dl.bintray.com/devexperts/Maven/" }
+}
+
+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
new file mode 100644
index 00000000..b5fad693
--- /dev/null
+++ b/gradle/compile-native-multiplatform.gradle
@@ -0,0 +1,30 @@
+kotlin {
+ targets {
+ if (project.ext.ideaActive) {
+ fromPreset(project.ext.ideaPreset, 'native')
+ } else {
+ fromPreset(presets.linuxX64, 'linuxX64')
+ fromPreset(presets.iosArm64, 'iosArm64')
+ fromPreset(presets.iosArm32, 'iosArm32')
+ fromPreset(presets.iosX64, 'iosX64')
+ fromPreset(presets.macosX64, 'macosX64')
+ fromPreset(presets.mingwX64, 'windowsX64')
+ }
+ }
+
+ sourceSets {
+ nativeMain { dependsOn commonMain }
+ // Empty source set is required in order to have native tests task
+ nativeTest {}
+
+ if (!project.ext.ideaActive) {
+ configure([linuxX64Main, macosX64Main, windowsX64Main, iosArm32Main, iosArm64Main, iosX64Main]) {
+ dependsOn nativeMain
+ }
+
+ configure([linuxX64Test, macosX64Test, windowsX64Test, iosArm32Test, iosArm64Test, iosX64Test]) {
+ dependsOn nativeTest
+ }
+ }
+ }
+}
diff --git a/gradle/dokka.gradle b/gradle/dokka.gradle
new file mode 100644
index 00000000..f1d8f21a
--- /dev/null
+++ b/gradle/dokka.gradle
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2019 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)
+
+ if (project.name != "kotlinx-coroutines-core") {
+ dependsOn(project.configurations.compileClasspath)
+ dependsOn(project.sourceSets.main.output)
+ afterEvaluate {
+ 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/experimental.gradle b/gradle/experimental.gradle
new file mode 100644
index 00000000..51bda6c5
--- /dev/null
+++ b/gradle/experimental.gradle
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// For new mpp
+ext.experimentalAnnotations = [
+ "kotlin.Experimental",
+ "kotlin.experimental.ExperimentalTypeInference",
+ "kotlin.ExperimentalMultiplatform",
+ "kotlinx.coroutines.ExperimentalCoroutinesApi",
+ "kotlinx.coroutines.ObsoleteCoroutinesApi",
+ "kotlinx.coroutines.InternalCoroutinesApi",
+ "kotlinx.coroutines.FlowPreview"]
diff --git a/gradle/maven-central.gradle b/gradle/maven-central.gradle
new file mode 100644
index 00000000..4f9df6ab
--- /dev/null
+++ b/gradle/maven-central.gradle
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// --------------- pom configuration ---------------
+
+def pomConfig = {
+ licenses {
+ license {
+ name "The Apache Software License, Version 2.0"
+ url "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ distribution "repo"
+ }
+ }
+ developers {
+ developer {
+ id "JetBrains"
+ name "JetBrains Team"
+ organization "JetBrains"
+ organizationUrl "https://www.jetbrains.com"
+ }
+ }
+
+ scm {
+ url "https://github.com/Kotlin/kotlinx.coroutines"
+ }
+}
+
+project.ext.configureMavenCentralMetadata = {
+ def root = it.asNode()
+ // NOTE: Don't try to move top-level things (especially "description") to the pomConfig block
+ // because they would resolve incorrectly to top-level project properties in Gradle/Groovy
+ root.appendNode('name', project.name)
+ root.appendNode('description', 'Coroutines support libraries for Kotlin')
+ root.appendNode('url', 'https://github.com/Kotlin/kotlinx.coroutines')
+ root.children().last() + pomConfig
+}
diff --git a/gradle/node-js.gradle b/gradle/node-js.gradle
new file mode 100644
index 00000000..208f4ad2
--- /dev/null
+++ b/gradle/node-js.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+apply plugin: 'com.moowork.node'
+
+node {
+ version = "$node_version"
+ npmVersion = "$npm_version"
+ download = true
+ nodeModulesDir = file(buildDir)
+}
+
+// Configures testing for JS modules
+
+task prepareNodePackage(type: Copy) {
+ from("npm") {
+ include 'package.json'
+ // Postpone expansion of package.json until we configure version property in build.gradle
+ def copySpec = it
+ afterEvaluate {
+ copySpec.expand(project.properties + [kotlinDependency: ""])
+ }
+ }
+ from("npm") {
+ exclude 'package.json'
+ }
+ into "$node.nodeModulesDir"
+}
+
+npmInstall.dependsOn prepareNodePackage
+
+// Workaround the problem with Node downloading
+repositories.whenObjectAdded {
+ if (it instanceof IvyArtifactRepository) {
+ metadataSources {
+ artifact()
+ }
+ }
+}
diff --git a/gradle/publish-bintray.gradle b/gradle/publish-bintray.gradle
new file mode 100644
index 00000000..dd528fe9
--- /dev/null
+++ b/gradle/publish-bintray.gradle
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// Configures publishing of Maven artifacts to Bintray
+
+apply plugin: 'maven'
+apply plugin: 'maven-publish'
+apply plugin: "com.github.johnrengelman.shadow"
+
+apply from: project.rootProject.file('gradle/maven-central.gradle')
+
+// ------------- tasks
+
+def isMultiplatform = project.name == "kotlinx-coroutines-core"
+def isBom = project.name == "kotlinx-coroutines-bom"
+
+if (!isMultiplatform) {
+ // Regular java modules need 'java-library' plugin for proper publication
+ apply plugin: 'java-library'
+
+ // MPP projects pack their sources automatically, java libraries need to explicitly pack them
+ task sourcesJar(type: Jar) {
+ archiveClassifier = 'sources'
+ from sourceSets.main.allSource
+ }
+}
+
+// empty xxx-javadoc.jar
+task javadocJar(type: Jar) {
+ archiveClassifier = 'javadoc'
+}
+
+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')
+ }
+ }
+ }
+
+ if (isBom) {
+ // Configure mavenBom publication
+ publications {
+ mavenBom(MavenPublication) {}
+ }
+ } else if (!isMultiplatform) {
+ // Configure java publications for regular non-MPP modules
+ publications {
+ maven(MavenPublication) {
+ if (project.name == "kotlinx-coroutines-debug") {
+ project.shadow.component(it)
+ } else {
+ from components.java
+ }
+ artifact sourcesJar
+ }
+ }
+ }
+
+ publications.all {
+ pom.withXml(configureMavenCentralMetadata)
+
+ // add empty javadocs (no need for MPP root publication which publishes only pom file)
+ if (it.name != 'kotlinMultiplatform' && !isBom) {
+ it.artifact(javadocJar)
+ }
+
+ // Rename MPP artifacts for backward compatibility
+ def type = it.name
+ switch (type) {
+ case 'kotlinMultiplatform':
+ it.artifactId = "$project.name-native"
+ break
+ case 'metadata':
+ it.artifactId = "$project.name-common"
+ break
+ case 'jvm':
+ it.artifactId = "$project.name"
+ break
+ case 'js':
+ case 'native':
+ it.artifactId = "$project.name-$type"
+ break
+ }
+
+ // disable metadata everywhere, but in native modules
+ if (type == 'maven' || type == 'metadata' || type == 'jvm' || type == 'js') {
+ moduleDescriptorGenerator = null
+ }
+ }
+}
+
+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) \ No newline at end of file
diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle
new file mode 100644
index 00000000..a2991db4
--- /dev/null
+++ b/gradle/publish-npm-js.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+def prop(name, defVal) {
+ def value = project.properties[name]
+ if (value == null) return defVal
+ return value
+}
+
+def distTag(version) {
+ def i = version.indexOf('-')
+ if (i > 0) return version.substring(i + 1)
+ return "latest"
+}
+
+def npmTemplateDir = file("$projectDir/npm")
+def npmDeployDir = file("$buildDir/npm")
+
+def authToken = prop("kotlin.npmjs.auth.token", "")
+def dryRun = prop("dryRun", "false")
+
+// Note: publish transformed files using dependency on sourceSets.main.output
+task preparePublishNpm(type: Copy) {
+ from(npmTemplateDir) {
+ // Postpone expansion of package.json until we configure version property in build.gradle
+ def copySpec = it
+ afterEvaluate {
+ copySpec.expand(project.properties + [kotlinDependency: "\"kotlin\": \"$kotlin_version\""])
+ }
+ }
+ // we must publish output that is transformed by atomicfu
+ from(kotlin.targets.js.compilations.main.output.allOutputs)
+ into npmDeployDir
+}
+
+task publishNpm(type: NpmTask, dependsOn: [preparePublishNpm]) {
+ workingDir = npmDeployDir
+
+ doFirst {
+ def npmDeployTag = distTag(version)
+ def deployArgs = ['publish',
+ "--//registry.npmjs.org/:_authToken=$authToken",
+ "--tag=$npmDeployTag"]
+ if (dryRun == "true") {
+ println("$npmDeployDir \$ npm arguments: $deployArgs")
+ args = ['pack']
+ } else {
+ args = deployArgs
+ }
+ }
+}
diff --git a/gradle/targets.gradle b/gradle/targets.gradle
new file mode 100644
index 00000000..d4e560fd
--- /dev/null
+++ b/gradle/targets.gradle
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+/*
+ * This is a hack to avoid creating unsupported native source sets when importing project into IDEA
+ */
+project.ext.ideaActive = System.getProperty('idea.active') == 'true'
+
+kotlin {
+ targets {
+ def manager = project.ext.hostManager
+ def linuxEnabled = manager.isEnabled(presets.linuxX64.konanTarget)
+ def macosEnabled = manager.isEnabled(presets.macosX64.konanTarget)
+ def winEnabled = manager.isEnabled(presets.mingwX64.konanTarget)
+
+ project.ext.isLinuxHost = linuxEnabled
+ project.ext.isMacosHost = macosEnabled
+ project.ext.isWinHost = winEnabled
+
+ if (project.ext.ideaActive) {
+ def ideaPreset = presets.linuxX64
+ if (macosEnabled) ideaPreset = presets.macosX64
+ if (winEnabled) ideaPreset = presets.mingwX64
+ project.ext.ideaPreset = ideaPreset
+ }
+ }
+}
diff --git a/gradle/test-mocha-js.gradle b/gradle/test-mocha-js.gradle
new file mode 100644
index 00000000..006864ba
--- /dev/null
+++ b/gradle/test-mocha-js.gradle
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// -- Testing with Mocha under Node
+
+task installDependenciesMochaNode(type: NpmTask, dependsOn: [npmInstall]) {
+ args = ['install',
+ "mocha@$mocha_version",
+ "source-map-support@$source_map_support_version",
+ '--no-save']
+ if (project.hasProperty("teamcity")) args += ["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+}
+
+// todo: use atomicfu-transformed test files here (not critical)
+task testMochaNode(type: NodeTask, dependsOn: [compileTestKotlinJs, installDependenciesMochaNode]) {
+ script = file("$node.nodeModulesDir/node_modules/mocha/bin/mocha")
+ args = [compileTestKotlinJs.outputFile, '--require', 'source-map-support/register']
+ if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+}
+
+jsTest.dependsOn testMochaNode
+
+// -- Testing with Mocha under headless Chrome
+
+task installDependenciesMochaChrome(type: NpmTask, dependsOn: [npmInstall]) {
+ args = ['install',
+ "mocha@$mocha_version",
+ "mocha-headless-chrome@$mocha_headless_chrome_version",
+ "kotlin@$kotlin_version",
+ "kotlin-test@$kotlin_version",
+ '--no-save']
+ if (project.hasProperty("teamcity")) args += [
+ "mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+}
+
+def mochaChromeTestPage = file("$buildDir/test-page.html")
+
+task prepareMochaChrome(dependsOn: [compileTestKotlinJs, installDependenciesMochaChrome]) {
+ outputs.file(mochaChromeTestPage)
+}
+
+prepareMochaChrome.doLast {
+ mochaChromeTestPage.text = """<!DOCTYPE html>
+ <html>
+ <head>
+ <title>Mocha Tests</title>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="$node.nodeModulesDir/node_modules/mocha/mocha.css">
+ </head>
+ <body>
+ <div id="mocha"></div>
+ <script src="$node.nodeModulesDir/node_modules/mocha/mocha.js"></script>
+ <script>mocha.setup('bdd');</script>
+ <script src="$node.nodeModulesDir/node_modules/kotlin/kotlin.js"></script>
+ <script src="$node.nodeModulesDir/node_modules/kotlin-test/kotlin-test.js"></script>
+ <script src="$compileKotlinJs.outputFile"></script>
+ <script src="$compileTestKotlinJs.outputFile"></script>
+ <script>mocha.run();</script>
+ </body>
+ </html>
+ """
+}
+
+task testMochaChrome(type: NodeTask, dependsOn: prepareMochaChrome) {
+ script = file("$node.nodeModulesDir/node_modules/mocha-headless-chrome/bin/start")
+ args = [compileTestKotlinJs.outputFile, '--file', mochaChromeTestPage]
+ if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+}
+
+// todo: Commented out because mocha-headless-chrome does not work on TeamCity
+//jsTest.dependsOn testMochaChrome
+
+// -- Testing with Mocha under jsdom
+
+task installDependenciesMochaJsdom(type: NpmTask, dependsOn: [npmInstall]) {
+ args = ['install',
+ "mocha@$mocha_version",
+ 'jsdom',
+ 'jsdom-global',
+ "source-map-support@$source_map_support_version",
+ '--no-save']
+ if (project.hasProperty("teamcity")) args += ["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+}
+
+task testMochaJsdom(type: NodeTask, dependsOn: [compileTestKotlinJs, installDependenciesMochaJsdom]) {
+ script = file("$node.nodeModulesDir/node_modules/mocha/bin/mocha")
+ args = [compileTestKotlinJs.outputFile, '--require', 'source-map-support/register', '--require', 'jsdom-global/register']
+ if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+}
+
+jsTest.dependsOn testMochaJsdom
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..28861d27
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..c141e334
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,9 @@
+#
+# Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..cccdd3d5
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## 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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# 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
new file mode 100644
index 00000000..f9553162
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@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 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=
+
+@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/integration/README.md b/integration/README.md
new file mode 100644
index 00000000..44f270c0
--- /dev/null
+++ b/integration/README.md
@@ -0,0 +1,27 @@
+# Coroutines integration
+
+This directory contains modules that provide integration with various asynchronous callback- and future-based libraries.
+Module name below corresponds to the artifact name in Maven/Gradle.
+
+## Modules
+
+* [kotlinx-coroutines-jdk8](kotlinx-coroutines-jdk8/README.md) -- integration with JDK8 `CompletableFuture` (Android API level 24).
+* [kotlinx-coroutines-guava](kotlinx-coroutines-guava/README.md) -- integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained).
+* [kotlinx-coroutines-slf4j](kotlinx-coroutines-slf4j/README.md) -- 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).
+
+## Contributing
+
+Follow the following simple guidelines when contributing integration with your favorite library:
+
+* Keep it simple and general. Ideally it should fit into a single file. If it does not fit, then consider
+ a separate GitHub project to host this integration.
+* Follow the example of other modules.
+ Cut-and-paste [kotlinx-coroutines-guava](kotlinx-coroutines-guava) module as a template.
+* Write tests and documentation, include top-level `README.md` with short overview and example.
+* Reference the new module from all the places:
+ * List of modules in this document.
+ * List of modules in top-level [`settings.gradle`](../settings.gradle).
+ * List of modules at the root of documentation site in [`site/docs/index.md`](../site/docs/index.md).
+* Update links to documentation website as explained [here](../knit/README.md#usage).
+* Squash your contribution to a single commit and create pull request to `develop` branch.
diff --git a/integration/kotlinx-coroutines-guava/README.md b/integration/kotlinx-coroutines-guava/README.md
new file mode 100644
index 00000000..4c43317a
--- /dev/null
+++ b/integration/kotlinx-coroutines-guava/README.md
@@ -0,0 +1,60 @@
+# Module kotlinx-coroutines-guava
+
+Integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained).
+
+Coroutine builders:
+
+| **Name** | **Result** | **Scope** | **Description**
+| -------- | ---------- | ---------- | ---------------
+| [future] | [ListenableFuture][com.google.common.util.concurrent.ListenableFuture] | [CoroutineScope] | Returns a single value with the future result
+
+Extension functions:
+
+| **Name** | **Description**
+| -------- | ---------------
+| [ListenableFuture.await][com.google.common.util.concurrent.ListenableFuture.await] | Awaits for completion of the future (cancellable)
+| [Deferred.asListenableFuture][kotlinx.coroutines.Deferred.asListenableFuture] | Converts a deferred value to the future
+
+## Example
+
+Given the following functions defined in some Java API based on Guava:
+
+```java
+public ListenableFuture<Image> loadImageAsync(String name); // starts async image loading
+public Image combineImages(Image image1, Image image2); // synchronously combines two images using some algorithm
+```
+
+We can consume this API from Kotlin coroutine to load two images and combine then asynchronously.
+The resulting function returns `ListenableFuture<Image>` for ease of use back from Guava-based Java code.
+
+```kotlin
+fun combineImagesAsync(name1: String, name2: String): ListenableFuture<Image> = future {
+ val future1 = loadImageAsync(name1) // start loading first image
+ val future2 = loadImageAsync(name2) // start loading second image
+ combineImages(future1.await(), future2.await()) // wait for both, combine, and return result
+}
+```
+
+Note that this module should be used only for integration with existing Java APIs based on `ListenableFuture`.
+Writing pure-Kotlin code that uses `ListenableFuture` is highly not recommended, since the resulting APIs based
+on the futures are quite error-prone. See the discussion on
+[Asynchronous Programming Styles](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#asynchronous-programming-styles)
+for details on general problems pertaining to any future-based API and keep in mind that `ListenableFuture` exposes
+a _blocking_ method
+[get](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html#get--)
+that makes it especially bad choice for coroutine-based Kotlin code.
+
+# Package kotlinx.coroutines.future
+
+Integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained).
+
+<!--- 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
+<!--- END -->
diff --git a/integration/kotlinx-coroutines-guava/build.gradle b/integration/kotlinx-coroutines-guava/build.gradle
new file mode 100644
index 00000000..9e44b998
--- /dev/null
+++ b/integration/kotlinx-coroutines-guava/build.gradle
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+ext.guava_version = '28.0-jre'
+
+dependencies {
+ compile "com.google.guava:guava:$guava_version"
+}
+
+tasks.withType(dokka.getClass()) {
+ externalDocumentationLink {
+ url = new URL("https://google.github.io/guava/releases/$guava_version/api/docs/")
+ packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
+ }
+} \ No newline at end of file
diff --git a/integration/kotlinx-coroutines-guava/package.list b/integration/kotlinx-coroutines-guava/package.list
new file mode 100644
index 00000000..9ad26f44
--- /dev/null
+++ b/integration/kotlinx-coroutines-guava/package.list
@@ -0,0 +1,16 @@
+com.google.common.annotations
+com.google.common.base
+com.google.common.cache
+com.google.common.collect
+com.google.common.escape
+com.google.common.eventbus
+com.google.common.graph
+com.google.common.hash
+com.google.common.html
+com.google.common.io
+com.google.common.math
+com.google.common.net
+com.google.common.primitives
+com.google.common.reflect
+com.google.common.util.concurrent
+com.google.common.xml
diff --git a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
new file mode 100644
index 00000000..de47c760
--- /dev/null
+++ b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2016-2018 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 com.google.common.util.concurrent.internal.InternalFutureFailureAccess
+import com.google.common.util.concurrent.internal.InternalFutures
+import java.util.concurrent.*
+import kotlin.coroutines.*
+import kotlinx.coroutines.*
+import java.util.concurrent.CancellationException
+
+/**
+ * Starts [block] in a new coroutine and returns a [ListenableFuture] pointing to its result.
+ *
+ * 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.
+ *
+ * `CoroutineContext` is inherited from this [CoroutineScope]. Additional context elements can be
+ * added/overlaid by passing [context].
+ *
+ * If the context does not have a [CoroutineDispatcher], nor any other [ContinuationInterceptor]
+ * member, [Dispatchers.Default] is used.
+ *
+ * The parent job is inherited from this [CoroutineScope], and can be overridden by passing
+ * a [Job] in [context].
+ *
+ * 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.
+ *
+ * @param context added overlaying [CoroutineScope.coroutineContext] to form the new context.
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the code to execute.
+ */
+public fun <T> CoroutineScope.future(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> T
+): ListenableFuture<T> {
+ require(!start.isLazy) { "$start start is not supported" }
+ val newContext = newCoroutineContext(context)
+ // TODO: It'd be nice not to leak this SettableFuture reference, which is easily blind-cast.
+ val future = SettableFuture.create<T>()
+ val coroutine = ListenableFutureCoroutine(newContext, future)
+ future.addListener(
+ coroutine,
+ MoreExecutors.directExecutor())
+ coroutine.start(start, coroutine, block)
+ return future
+}
+
+/**
+ * Returns a [Deferred] that is completed or failed by `this` [ListenableFuture].
+ *
+ * Completion is non-atomic between the two promises.
+ *
+ * Cancellation is propagated bidirectionally.
+ *
+ * When `this` `ListenableFuture` completes (either successfully or exceptionally) it will try to
+ * complete the returned `Deferred` with the same value or exception. This will succeed, barring a
+ * race with cancellation of the `Deferred`.
+ *
+ * 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
+ * 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`.
+ */
+public fun <T> ListenableFuture<T>.asDeferred(): Deferred<T> {
+ /* This method creates very specific behaviour as it entangles the `Deferred` and
+ * `ListenableFuture`. This behaviour is the best discovered compromise between the possible
+ * states and interface contracts of a `Future` and the states of a `Deferred`. The specific
+ * behaviour is described here.
+ *
+ * When `this` `ListenableFuture` is successfully cancelled - meaning
+ * `ListenableFuture.cancel()` returned `true` - it will synchronously cancel the returned
+ * `Deferred`. This can only race with cancellation of the returned `Deferred`, so the
+ * `Deferred` will always be put into its "cancelling" state and (barring uncooperative
+ * cancellation) _eventually_ reach its "cancelled" state when either promise is successfully
+ * cancelled.
+ *
+ * When the returned `Deferred` is cancelled, `ListenableFuture.cancel()` will be synchronously
+ * called on `this` `ListenableFuture`. This will attempt to cancel the `Future`, though
+ * cancellation may not succeed and the `ListenableFuture` may complete in a non-cancelled
+ * terminal state.
+ *
+ * The returned `Deferred` may receive and suppress the `true` return value from
+ * `ListenableFuture.cancel()` when the task is cancelled via the `Deferred` reference to it.
+ * This is unavoidable, so make sure no idempotent cancellation work is performed by a
+ * reference-holder of the `ListenableFuture` task. The idempotent work won't get done if
+ * cancellation was from the `Deferred` representation of the task.
+ *
+ * This is inherently a race. See `Future.cancel()` for a description of `Future` cancellation
+ * semantics. See `Job` for a description of coroutine cancellation semantics.
+ */
+ // First, try the fast-fast error path for Guava ListenableFutures. This will save allocating an
+ // Exception by using the same instance the Future created.
+ if (this is InternalFutureFailureAccess) {
+ val t: Throwable? = InternalFutures.tryInternalFastPathGetFailure(this)
+ if (t != null) {
+ return CompletableDeferred<T>().also {
+ it.completeExceptionally(t)
+ }
+ }
+ }
+
+ // Second, try the fast path for a completed Future. The Future is known to be done, so get()
+ // will not block, and thus it won't be interrupted. Calling getUninterruptibly() instead of
+ // getDone() in this known-non-interruptible case saves the volatile read that getDone() uses to
+ // handle interruption.
+ if (isDone) {
+ return try {
+ val value = Uninterruptibles.getUninterruptibly(this)
+ if (value == null) {
+ CompletableDeferred<T>().also {
+ it.completeExceptionally(KotlinNullPointerException())
+ }
+ } else {
+ CompletableDeferred(value)
+ }
+ } catch (e: CancellationException) {
+ CompletableDeferred<T>().also { it.cancel(e) }
+ } 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
+ // Future implementation.
+ CompletableDeferred<T>().also { it.completeExceptionally(e.nonNullCause()) }
+ }
+ }
+
+ // Finally, if this isn't done yet, attach a Listener that will complete the Deferred.
+ val deferred = CompletableDeferred<T>()
+ Futures.addCallback(this, object : FutureCallback<T> {
+ override fun onSuccess(result: T?) {
+ deferred.complete(result!!)
+ }
+
+ override fun onFailure(t: Throwable) {
+ deferred.completeExceptionally(t)
+ }
+ }, MoreExecutors.directExecutor())
+
+ // ... And cancel the Future when the deferred completes. Since the return type of this method
+ // is Deferred, the only interaction point from the caller is to cancel the Deferred. If this
+ // completion handler runs before the Future is completed, the Deferred must have been
+ // cancelled and should propagate its cancellation. If it runs after the Future is completed,
+ // this is a no-op.
+ deferred.invokeOnCompletion {
+ cancel(false)
+ }
+ return deferred
+}
+
+/**
+ * Returns the cause from an [ExecutionException] thrown by a [Future.get] or similar.
+ *
+ * [ExecutionException] _always_ wraps a non-null cause when Future.get() throws. A Future cannot
+ * fail without a non-null `cause`, because the only way a Future _can_ fail is an uncaught
+ * [Exception].
+ *
+ * If this !! throws [NullPointerException], a Future is breaking its interface contract and losing
+ * state - a serious fundamental bug.
+ */
+private fun ExecutionException.nonNullCause(): Throwable {
+ return this.cause!!
+}
+
+/**
+ * Returns a [ListenableFuture] that is completed or failed by `this` [Deferred].
+ *
+ * Completion is non-atomic between the two promises.
+ *
+ * When either promise successfully completes, it will attempt to synchronously complete its
+ * counterpart with the same value. This will succeed barring a race with cancellation.
+ *
+ * When either promise completes with an Exception, it will attempt to synchronously complete its
+ * counterpart with the same Exception. This will succeed barring a race with cancellation.
+ *
+ * Cancellation is propagated bidirectionally.
+ *
+ * When the returned [Future] is successfully cancelled - meaning [Future.cancel] returned true -
+ * [Deferred.cancel] will be synchronously called on `this` [Deferred]. This will attempt to cancel
+ * the `Deferred`, though cancellation may not succeed and the `Deferred` may complete in a
+ * non-cancelled terminal state.
+ *
+ * When `this` `Deferred` reaches its "cancelled" state with a successful cancellation - meaning it
+ * completes with [kotlinx.coroutines.CancellationException] - `this` `Deferred` will synchronously
+ * cancel the returned `Future`. This can only race with cancellation of the returned `Future`, so
+ * the returned `Future` will always _eventually_ reach its cancelled state when either promise is
+ * successfully cancelled, for their different meanings of "successfully cancelled".
+ *
+ * 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
+ * corner cases of this method.
+ */
+public fun <T> Deferred<T>.asListenableFuture(): ListenableFuture<T> {
+ val outerFuture = OuterFuture<T>(this)
+ outerFuture.afterInit()
+ return outerFuture
+}
+
+/**
+ * Awaits completion of `this` [ListenableFuture] without blocking a thread.
+ *
+ * This suspend 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 future and immediately resumes with [CancellationException][kotlinx.coroutines.CancellationException].
+ *
+ * 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 {
+ if (isDone) return Uninterruptibles.getUninterruptibly(this)
+ } catch (e: ExecutionException) {
+ // ExecutionException is the only kind of exception that can be thrown from a gotten
+ // Future, other than CancellationException. Cancellation is propagated upward so that
+ // the coroutine running this suspend function may process it.
+ // Any other Exception showing up here indicates a very fundamental bug in a
+ // Future implementation.
+ throw e.nonNullCause()
+ }
+
+ return suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
+ addListener(
+ ToContinuation(this, cont),
+ MoreExecutors.directExecutor())
+ cont.invokeOnCancellation {
+ cancel(false)
+ }
+ }
+}
+
+/**
+ * Propagates the outcome of [futureToObserve] to [continuation] on completion.
+ *
+ * Cancellation is propagated as cancelling the continuation. If [futureToObserve] completes
+ * and fails, the cause of the Future will be propagated without a wrapping
+ * [ExecutionException] when thrown.
+ */
+private class ToContinuation<T>(
+ val futureToObserve: ListenableFuture<T>,
+ val continuation: CancellableContinuation<T>
+): Runnable {
+ override fun run() {
+ if (futureToObserve.isCancelled) {
+ continuation.cancel()
+ } else {
+ try {
+ continuation.resumeWith(
+ Result.success(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
+ // Future implementation.
+ continuation.resumeWithException(e.nonNullCause())
+ }
+ }
+ }
+}
+
+/**
+ * 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.
+ */
+private class ListenableFutureCoroutine<T>(
+ context: CoroutineContext,
+ private val future: SettableFuture<T>
+) : AbstractCoroutine<T>(context), Runnable {
+
+ /**
+ * 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()
+ }
+ }
+
+ override fun onCompleted(value: T) {
+ future.set(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
+ handleCoroutineException(context, cause)
+ }
+ }
+}
+
+/**
+ * A [ListenableFuture] that delegates to an internal [DeferredListenableFuture], collaborating with
+ * it.
+ *
+ * This setup allows the returned [ListenableFuture] to maintain the following properties:
+ *
+ * - Correct implementation of [Future]'s happens-after semantics documented for [get], [isDone]
+ * and [isCancelled] methods
+ * - Cancellation propagation both to and from [Deferred]
+ * - Correct cancellation and completion semantics even when this [ListenableFuture] is combined
+ * with different concrete implementations of [ListenableFuture]
+ * - 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
+ * cost of the implementation's readability.
+ */
+private class OuterFuture<T>(private val deferred: Deferred<T>): ListenableFuture<T> {
+ val innerFuture = DeferredListenableFuture(deferred)
+
+ // 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()
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * See [DeferredListenableFuture.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
+ // 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)
+ }
+
+ /**
+ * 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].
+ *
+ * `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()`.
+ */
+ override fun get(): T {
+ return getInternal(innerFuture.get())
+ }
+
+ /** See [get()]. */
+ override fun get(timeout: Long, unit: TimeUnit): T {
+ return getInternal(innerFuture.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()
+ }
+ }
+
+ override fun addListener(listener: Runnable, executor: Executor) {
+ innerFuture.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)
+ }
+
+ /**
+ * Tries to cancel the task. 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.
+ *
+ * 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
+ * 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()
+ true
+ } else {
+ false
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-guava/test/ListenableFutureExceptionsTest.kt b/integration/kotlinx-coroutines-guava/test/ListenableFutureExceptionsTest.kt
new file mode 100644
index 00000000..7417168b
--- /dev/null
+++ b/integration/kotlinx-coroutines-guava/test/ListenableFutureExceptionsTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-2018 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.base.*
+import com.google.common.util.concurrent.*
+import kotlinx.coroutines.*
+import org.junit.Test
+import java.io.*
+import java.util.concurrent.*
+import kotlin.test.*
+
+class ListenableFutureExceptionsTest : TestBase() {
+
+ @Test
+ fun testAwait() {
+ testException(IOException(), { it is IOException })
+ }
+
+ @Test
+ fun testAwaitChained() {
+ testException(IOException(), { it is IOException }, { i -> i!! + 1 })
+ }
+
+ @Test
+ fun testAwaitCompletionException() {
+ testException(CompletionException("test", IOException()), { it is CompletionException })
+ }
+
+ @Test
+ fun testAwaitChainedCompletionException() {
+ testException(
+ CompletionException("test", IOException()),
+ { it is CompletionException },
+ { i -> i!! + 1 })
+ }
+
+ @Test
+ fun testAwaitTestException() {
+ testException(TestException(), { it is TestException })
+ }
+
+ @Test
+ fun testAwaitChainedTestException() {
+ testException(TestException(), { it is TestException }, { i -> i!! + 1 })
+ }
+
+ private fun testException(
+ exception: Throwable,
+ expected: ((Throwable) -> Boolean),
+ transformer: ((Int?) -> Int?)? = null
+ ) {
+
+ // Fast path
+ runTest {
+ val future = SettableFuture.create<Int>()
+ val chained = if (transformer == null) {
+ future
+ } else {
+ Futures.transform(future, Function(transformer), MoreExecutors.directExecutor())
+ }
+ future.setException(exception)
+ try {
+ chained.await()
+ } catch (e: Throwable) {
+ assertTrue(expected(e))
+ }
+ }
+
+ // Slow path
+ runTest {
+ val future = SettableFuture.create<Int>()
+ val chained = if (transformer == null) {
+ future
+ } else {
+ Futures.transform(future, Function(transformer), MoreExecutors.directExecutor())
+ }
+ launch {
+ future.setException(exception)
+ }
+
+ try {
+ chained.await()
+ } catch (e: Throwable) {
+ assertTrue(expected(e))
+ }
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
new file mode 100644
index 00000000..f56a0be4
--- /dev/null
+++ b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+import org.junit.Test
+import java.util.concurrent.*
+import java.util.concurrent.CancellationException
+
+class ListenableFutureTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("ForkJoinPool.commonPool-worker-")
+ }
+
+ @Test
+ fun testSimpleAwait() {
+ val service = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = GlobalScope.future {
+ service.submit(Callable<String> {
+ "O"
+ }).await() + "K"
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testAwaitWithContext() = runTest {
+ val future = SettableFuture.create<Int>()
+ val deferred = async {
+ withContext(Dispatchers.Default) {
+ future.await()
+ }
+ }
+
+ future.set(1)
+ assertEquals(1, deferred.await())
+ }
+
+ @Test
+ fun testAwaitWithCancellation() = runTest(expected = {it is TestCancellationException}) {
+ val future = SettableFuture.create<Int>()
+ val deferred = async {
+ withContext(Dispatchers.Default) {
+ future.await()
+ }
+ }
+
+ deferred.cancel(TestCancellationException())
+ deferred.await() // throws TCE
+ expectUnreached()
+ }
+
+ @Test
+ fun testCompletedFuture() {
+ val toAwait = SettableFuture.create<String>()
+ toAwait.set("O")
+ val future = GlobalScope.future {
+ toAwait.await() + "K"
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testWaitForFuture() {
+ val toAwait = SettableFuture.create<String>()
+ val future = GlobalScope.future {
+ toAwait.await() + "K"
+ }
+ assertFalse(future.isDone)
+ toAwait.set("O")
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testCompletedFutureExceptionally() {
+ val toAwait = SettableFuture.create<String>()
+ toAwait.setException(IllegalArgumentException("O"))
+ val future = GlobalScope.future {
+ try {
+ toAwait.await()
+ } catch (e: RuntimeException) {
+ assertThat(e, IsInstanceOf(IllegalArgumentException::class.java))
+ e.message!!
+ } + "K"
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testWaitForFutureWithException() {
+ val toAwait = SettableFuture.create<String>()
+ val future = GlobalScope.future {
+ try {
+ toAwait.await()
+ } catch (e: RuntimeException) {
+ assertThat(e, IsInstanceOf(IllegalArgumentException::class.java))
+ e.message!!
+ } + "K"
+ }
+ assertFalse(future.isDone)
+ toAwait.setException(IllegalArgumentException("O"))
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testExceptionInsideCoroutine() {
+ val service = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = GlobalScope.future {
+ if (service.submit(Callable<Boolean> { true }).await()) {
+ throw IllegalStateException("OK")
+ }
+ "fail"
+ }
+ try {
+ future.get()
+ fail("'get' should've throw an exception")
+ } catch (e: ExecutionException) {
+ assertThat(e.cause, IsInstanceOf(IllegalStateException::class.java))
+ assertThat(e.cause!!.message, IsEqual("OK"))
+ }
+ }
+
+ @Test
+ fun testFutureLazyStartThrows() {
+ expect(1)
+ val e = assertFailsWith<IllegalArgumentException> {
+ GlobalScope.future(start = CoroutineStart.LAZY) {}
+ }
+
+ assertThat(e.message, IsEqual("LAZY start is not supported"))
+ finish(2)
+ }
+
+ @Test
+ fun testCompletedDeferredAsListenableFuture() = runBlocking {
+ expect(1)
+ val deferred = async(start = CoroutineStart.UNDISPATCHED) {
+ expect(2) // completed right away
+ "OK"
+ }
+ expect(3)
+ val future = deferred.asListenableFuture()
+ assertThat(future.await(), IsEqual("OK"))
+ finish(4)
+ }
+
+ @Test
+ fun testWaitForDeferredAsListenableFuture() = runBlocking {
+ expect(1)
+ val deferred = async {
+ expect(3) // will complete later
+ "OK"
+ }
+ expect(2)
+ val future = deferred.asListenableFuture()
+ assertThat(future.await(), IsEqual("OK")) // await yields main thread to deferred coroutine
+ finish(4)
+ }
+
+ @Test
+ fun testAsListenableFutureThrowable() {
+ val deferred = GlobalScope.async {
+ throw OutOfMemoryError()
+ }
+
+ val future = deferred.asListenableFuture()
+ try {
+ future.get()
+ } catch (e: ExecutionException) {
+ assertTrue(future.isDone)
+ assertTrue(e.cause is OutOfMemoryError)
+ }
+ }
+
+ @Test
+ fun testCancellableAwait() = runBlocking {
+ expect(1)
+ val toAwait = SettableFuture.create<String>()
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ toAwait.await() // suspends
+ } catch (e: CancellationException) {
+ expect(5) // should throw cancellation exception
+ throw e
+ }
+ }
+ expect(3)
+ job.cancel() // cancel the job
+ toAwait.set("fail") // too late, the waiting job was already cancelled
+ expect(4) // job processing of cancellation was scheduled, not executed yet
+ yield() // yield main thread to job
+ finish(6)
+ }
+
+ @Test
+ fun testFutureAwaitCancellationPropagatingToDeferred() = runTest {
+
+ val latch = CountDownLatch(1)
+ val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = executor.submit(Callable { latch.await(); 42 })
+ val deferred = async {
+ expect(2)
+ future.await()
+ }
+ expect(1)
+ yield()
+ future.cancel(/*mayInterruptIfRunning=*/true)
+ expect(3)
+ latch.countDown()
+ deferred.join()
+ assertTrue(future.isCancelled)
+ assertTrue(deferred.isCancelled)
+ assertFailsWith<CancellationException> { future.get() }
+ finish(4)
+ }
+
+ @Test
+ fun testFutureAwaitCancellationPropagatingToDeferredNoInterruption() = runTest {
+
+ val latch = CountDownLatch(1)
+ val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = executor.submit(Callable { latch.await(); 42 })
+ val deferred = async {
+ expect(2)
+ future.await()
+ }
+ expect(1)
+ yield()
+ future.cancel(/*mayInterruptIfRunning=*/false)
+ expect(3)
+ latch.countDown()
+ deferred.join()
+ assertTrue(future.isCancelled)
+ assertTrue(deferred.isCancelled)
+ assertFailsWith<CancellationException> { future.get() }
+ finish(4)
+ }
+
+ @Test
+ fun testAsListenableFutureCancellationPropagatingToDeferred() = runTest {
+ val latch = CountDownLatch(1)
+ val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = executor.submit(Callable { latch.await(); 42 })
+ val deferred = async {
+ expect(2)
+ future.await()
+ }
+ val asListenableFuture = deferred.asListenableFuture()
+ expect(1)
+ yield()
+ asListenableFuture.cancel(/*mayInterruptIfRunning=*/true)
+ expect(3)
+ latch.countDown()
+ deferred.join()
+ assertTrue(future.isCancelled)
+ assertTrue(deferred.isCancelled)
+ assertTrue(asListenableFuture.isCancelled)
+ assertFailsWith<CancellationException> { future.get() }
+ finish(4)
+ }
+
+ @Test
+ fun testAsListenableFutureCancellationPropagatingToDeferredNoInterruption() = runTest {
+ val latch = CountDownLatch(1)
+ val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = executor.submit(Callable { latch.await(); 42 })
+ val deferred = async {
+ expect(2)
+ future.await()
+ }
+ val asListenableFuture = deferred.asListenableFuture()
+ expect(1)
+ yield()
+ asListenableFuture.cancel(/*mayInterruptIfRunning=*/false)
+ expect(3)
+ latch.countDown()
+ deferred.join()
+ assertFailsWith<CancellationException> { asListenableFuture.get() }
+ assertTrue(future.isCancelled)
+ assertTrue(asListenableFuture.isCancelled)
+ assertTrue(deferred.isCancelled)
+ assertFailsWith<CancellationException> { future.get() }
+ finish(4)
+ }
+
+ @Test
+ fun testAsListenableFutureCancellationThroughSetFuture() = runTest {
+ val latch = CountDownLatch(1)
+ val future = SettableFuture.create<Void>()
+ val deferred = async {
+ expect(2)
+ future.await()
+ }
+ val asListenableFuture = deferred.asListenableFuture()
+ expect(1)
+ yield()
+ future.setFuture(Futures.immediateCancelledFuture())
+ expect(3)
+ latch.countDown()
+ deferred.join()
+ assertFailsWith<CancellationException> { asListenableFuture.get() }
+ // Future was not interrupted, but also wasn't blocking, so it will be successfully
+ // cancelled by its parent Coroutine.
+ assertTrue(future.isCancelled)
+ assertTrue(asListenableFuture.isCancelled)
+ assertTrue(deferred.isCancelled)
+ assertFailsWith<CancellationException> { future.get() }
+ finish(4)
+ }
+
+ @Test
+ fun testFutureCancellation() = runTest {
+ val future = awaitFutureWithCancel(true)
+ assertTrue(future.isCancelled)
+ assertFailsWith<CancellationException> { future.get() }
+ finish(4)
+ }
+
+ @Test
+ fun testAsListenableDeferredCancellationCauseAndMessagePropagate() = runTest {
+ val deferred = CompletableDeferred<Int>()
+ val inputCancellationException = CancellationException("Foobar")
+ inputCancellationException.initCause(OutOfMemoryError("Foobaz"))
+ deferred.cancel(inputCancellationException)
+ val asFuture = deferred.asListenableFuture()
+
+ val outputCancellationException =
+ assertFailsWith<CancellationException> { asFuture.get() }
+ assertEquals(outputCancellationException.message, "Foobar")
+ assertTrue(outputCancellationException.cause is OutOfMemoryError)
+ assertEquals(outputCancellationException.cause?.message, "Foobaz")
+ }
+
+ @Test
+ fun testNoFutureCancellation() = runTest {
+ val future = awaitFutureWithCancel(false)
+ assertFalse(future.isCancelled)
+ assertEquals(42, future.get())
+ finish(4)
+ }
+
+ @Test
+ fun testCancelledDeferredAsListenableFutureAwaitThrowsCancellation() = runTest {
+ val future = Futures.immediateCancelledFuture<Int>()
+ val asDeferred = future.asDeferred()
+ val asDeferredAsFuture = asDeferred.asListenableFuture()
+
+ assertTrue(asDeferredAsFuture.isCancelled)
+ assertFailsWith<CancellationException> {
+ val value: Int = asDeferredAsFuture.await()
+ }
+ }
+
+ @Test
+ fun testCancelledDeferredAsListenableFutureAsDeferredPassesCancellationAlong() = runTest {
+ val deferred = CompletableDeferred<Int>()
+ deferred.completeExceptionally(CancellationException())
+ val asFuture = deferred.asListenableFuture()
+ val asFutureAsDeferred = asFuture.asDeferred()
+
+ assertTrue(asFutureAsDeferred.isCancelled)
+ assertTrue(asFutureAsDeferred.isCompleted)
+ // By documentation, join() shouldn't throw when asDeferred is already complete.
+ asFutureAsDeferred.join()
+ assertThat(
+ asFutureAsDeferred.getCompletionExceptionOrNull(),
+ IsInstanceOf(CancellationException::class.java))
+ }
+
+ @Test
+ fun testCancelledFutureAsDeferredAwaitThrowsCancellation() = runTest {
+ val future = Futures.immediateCancelledFuture<Int>()
+ val asDeferred = future.asDeferred()
+
+ assertTrue(asDeferred.isCancelled)
+ assertFailsWith<CancellationException> {
+ val value: Int = asDeferred.await()
+ }
+ }
+
+ @Test
+ fun testCancelledFutureAsDeferredJoinDoesNotThrow() = runTest {
+ val future = Futures.immediateCancelledFuture<Int>()
+ val asDeferred = future.asDeferred()
+
+ assertTrue(asDeferred.isCancelled)
+ assertTrue(asDeferred.isCompleted)
+ // By documentation, join() shouldn't throw when asDeferred is already complete.
+ asDeferred.join()
+ assertThat(
+ asDeferred.getCompletionExceptionOrNull(),
+ IsInstanceOf(CancellationException::class.java))
+ }
+
+ @Test
+ fun testCompletedFutureAsDeferred() = runTest {
+ val future = SettableFuture.create<Int>()
+ val task = async {
+ expect(2)
+ assertEquals(42, future.asDeferred().await())
+ expect(4)
+ }
+
+ expect(1)
+ yield()
+ expect(3)
+ future.set(42)
+ task.join()
+ finish(5)
+ }
+
+ @Test
+ fun testFailedFutureAsDeferred() = runTest {
+ val future = SettableFuture.create<Int>().apply {
+ setException(TestException())
+ }
+ val deferred = future.asDeferred()
+ assertTrue(deferred.isCancelled && deferred.isCompleted)
+ val completionException = deferred.getCompletionExceptionOrNull()!!
+ assertTrue(completionException is TestException)
+
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: Throwable) {
+ assertTrue(e is TestException)
+ }
+ }
+
+ @Test
+ fun testFutureCompletedWithNullAsDeferred() = runTest {
+ val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = executor.submit(Callable { null })
+ val deferred = GlobalScope.async {
+ future.asDeferred().await()
+ }
+
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: Throwable) {
+ assertTrue(e is KotlinNullPointerException)
+ }
+ }
+
+ @Test
+ fun testThrowingFutureAsDeferred() = runTest {
+ val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = executor.submit(Callable { throw TestException() })
+ val deferred = GlobalScope.async {
+ future.asDeferred().await()
+ }
+
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: Throwable) {
+ assertTrue(e is TestException)
+ }
+ }
+
+ @Test
+ fun testStructuredException() = runTest(
+ expected = { it is TestException } // exception propagates to parent with structured concurrency
+ ) {
+ val result = future<Int>(Dispatchers.Unconfined) {
+ throw TestException("FAIL")
+ }
+ result.checkFutureException<TestException>()
+ }
+
+ @Test
+ fun testChildException() = runTest(
+ expected = { it is TestException } // exception propagates to parent with structured concurrency
+ ) {
+ val result = future(Dispatchers.Unconfined) {
+ // child crashes
+ launch { throw TestException("FAIL") }
+ 42
+ }
+ result.checkFutureException<TestException>()
+ }
+
+ @Test
+ fun testExternalCancellation() = runTest {
+ val future = future(Dispatchers.Unconfined) {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(2)
+ }
+ }
+
+ yield()
+ expect(1)
+ future.cancel(true)
+ finish(3)
+ }
+
+ @Test
+ fun testExceptionOnExternalCancellation() = runTest(expected = {it is TestException}) {
+ expect(1)
+ val result = future(Dispatchers.Unconfined) {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(2)
+ throw TestException()
+ }
+ }
+ result.cancel(true)
+ finish(3)
+ }
+
+ @Test
+ fun testUnhandledExceptionOnExternalCancellation() = runTest(
+ unhandled = listOf(
+ { it -> it is TestException } // exception is unhandled because there is no parent
+ )
+ ) {
+ 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 TestException() // this exception cannot be handled
+ }
+ }
+ result.cancel(true)
+ finish(3)
+ }
+
+ private inline fun <reified T: Throwable> ListenableFuture<*>.checkFutureException() {
+ val e = assertFailsWith<ExecutionException> { get() }
+ val cause = e.cause!!
+ assertTrue(cause is T)
+ }
+
+ private suspend fun CoroutineScope.awaitFutureWithCancel(cancellable: Boolean): ListenableFuture<Int> {
+ val latch = CountDownLatch(1)
+ val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
+ val future = executor.submit(Callable { latch.await(); 42 })
+ val deferred = async {
+ expect(2)
+ if (cancellable) future.await()
+ else future.asDeferred().await()
+ }
+ expect(1)
+ yield()
+ deferred.cancel()
+ expect(3)
+ latch.countDown()
+ return future
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/README.md b/integration/kotlinx-coroutines-jdk8/README.md
new file mode 100644
index 00000000..3a204416
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/README.md
@@ -0,0 +1,64 @@
+# Module kotlinx-coroutines-jdk8
+
+Integration with JDK8 [CompletableFuture] (Android API level 24).
+
+Coroutine builders:
+
+| **Name** | **Result** | **Scope** | **Description**
+| -------- | ------------------- | ---------------- | ---------------
+| [future] | [CompletableFuture] | [CoroutineScope] | Returns a single value with the future result
+
+Extension functions:
+
+| **Name** | **Description**
+| -------- | ---------------
+| [CompletionStage.await][java.util.concurrent.CompletionStage.await] | Awaits for completion of the completion stage
+| [CompletionStage.asDeferred][java.util.concurrent.CompletionStage.asDeferred] | Converts completion stage to an instance of [Deferred]
+| [Deferred.asCompletableFuture][kotlinx.coroutines.Deferred.asCompletableFuture] | Converts a deferred value to the future
+
+## Example
+
+Given the following functions defined in some Java API:
+
+```java
+public CompletableFuture<Image> loadImageAsync(String name); // starts async image loading
+public Image combineImages(Image image1, Image image2); // synchronously combines two images using some algorithm
+```
+
+We can consume this API from Kotlin coroutine to load two images and combine then asynchronously.
+The resulting function returns `CompletableFuture<Image>` for ease of use back from Java.
+
+```kotlin
+fun combineImagesAsync(name1: String, name2: String): CompletableFuture<Image> = future {
+ val future1 = loadImageAsync(name1) // start loading first image
+ val future2 = loadImageAsync(name2) // start loading second image
+ combineImages(future1.await(), future2.await()) // wait for both, combine, and return result
+}
+```
+
+Note that this module should be used only for integration with existing Java APIs based on `CompletableFuture`.
+Writing pure-Kotlin code that uses `CompletableFuture` is highly not recommended, since the resulting APIs based
+on the futures are quite error-prone. See the discussion on
+[Asynchronous Programming Styles](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md#asynchronous-programming-styles)
+for details on general problems pertaining to any future-based API and keep in mind that `CompletableFuture` exposes
+a _blocking_ method
+[get](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html#get--)
+that makes it especially bad choice for coroutine-based Kotlin code.
+
+# Package kotlinx.coroutines.future
+
+Integration with JDK8 [CompletableFuture] (Android API level 24).
+
+[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html
+
+<!--- 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
+<!--- END -->
diff --git a/integration/kotlinx-coroutines-jdk8/build.gradle b/integration/kotlinx-coroutines-jdk8/build.gradle
new file mode 100644
index 00000000..3b17101f
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/build.gradle
@@ -0,0 +1,4 @@
+/*
+ * Copyright 2016-2018 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
new file mode 100644
index 00000000..164ee2d2
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/src/future/Future.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.future
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
+import java.util.concurrent.*
+import java.util.function.*
+import kotlin.coroutines.*
+
+/**
+ * Starts new coroutine and returns its result as an implementation of [CompletableFuture].
+ * The running coroutine is cancelled when the resulting future is cancelled or otherwise completed.
+ *
+ * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
+ * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
+ * with corresponding [coroutineContext] element.
+ *
+ * By default, the coroutine is immediately scheduled for execution.
+ * Other options can be specified via `start` parameter. See [CoroutineStart] for details.
+ * A value of [CoroutineStart.LAZY] is not supported
+ * (since `CompletableFuture` framework does not provide the corresponding capability) and
+ * produces [IllegalArgumentException].
+ *
+ * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
+ *
+ * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the coroutine code.
+ */
+public fun <T> CoroutineScope.future(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> T
+) : CompletableFuture<T> {
+ require(!start.isLazy) { "$start start is not supported" }
+ val newContext = this.newCoroutineContext(context)
+ val future = CompletableFuture<T>()
+ val coroutine = CompletableFutureCoroutine(newContext, future)
+ future.whenComplete(coroutine) // Cancel coroutine if future was completed externally
+ coroutine.start(start, coroutine, block)
+ return future
+}
+
+private class CompletableFutureCoroutine<T>(
+ context: CoroutineContext,
+ private val future: CompletableFuture<T>
+) : AbstractCoroutine<T>(context), BiConsumer<T?, Throwable?> {
+ override fun accept(value: T?, exception: Throwable?) {
+ cancel()
+ }
+
+ override fun onCompleted(value: T) {
+ future.complete(value)
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ if (!future.completeExceptionally(cause) && !handled) {
+ // prevents loss of exception that was not handled by parent & could not be set to CompletableFuture
+ handleCoroutineException(context, cause)
+ }
+ }
+}
+
+/**
+ * Converts this deferred value to the instance of [CompletableFuture].
+ * The deferred value is cancelled when the resulting future is cancelled or otherwise completed.
+ */
+public fun <T> Deferred<T>.asCompletableFuture(): CompletableFuture<T> {
+ val future = CompletableFuture<T>()
+ setupCancellation(future)
+ invokeOnCompletion {
+ try {
+ future.complete(getCompleted())
+ } catch (t: Throwable) {
+ future.completeExceptionally(t)
+ }
+ }
+ return future
+}
+
+/**
+ * Converts this job to the instance of [CompletableFuture].
+ * The job is cancelled when the resulting future is cancelled or otherwise completed.
+ */
+public fun Job.asCompletableFuture(): CompletableFuture<Unit> {
+ val future = CompletableFuture<Unit>()
+ setupCancellation(future)
+ invokeOnCompletion { cause ->
+ if (cause === null) future.complete(Unit)
+ else future.completeExceptionally(cause)
+ }
+ return future
+}
+
+private fun Job.setupCancellation(future: CompletableFuture<*>) {
+ future.whenComplete { _, exception ->
+ cancel(exception?.let {
+ it as? CancellationException ?: CancellationException("CompletableFuture was completed exceptionally", it)
+ })
+ }
+}
+
+/**
+ * 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.
+ */
+public fun <T> CompletionStage<T>.asDeferred(): Deferred<T> {
+ // Fast path if already completed
+ if (this is Future<*> && isDone()){
+ return try {
+ @Suppress("UNCHECKED_CAST")
+ CompletableDeferred(get() as T)
+ } catch (e: Throwable) {
+ // unwrap original cause from ExecutionException
+ val original = (e as? ExecutionException)?.cause ?: e
+ CompletableDeferred<T>().also { it.completeExceptionally(original) }
+ }
+ }
+ 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)
+ }
+ }
+ if (this is Future<*>) result.cancelFutureOnCompletion(this)
+ return result
+}
+
+/**
+ * Awaits for completion of the completion stage 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.
+ */
+public suspend fun <T> CompletionStage<T>.await(): T {
+ // fast path when CompletableFuture is already done (does not suspend)
+ if (this is Future<*> && isDone()) {
+ try {
+ @Suppress("UNCHECKED_CAST")
+ return get() as T
+ } catch (e: ExecutionException) {
+ throw e.cause ?: e // unwrap original cause from ExecutionException
+ }
+ }
+ // slow path -- suspend
+ return suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
+ val consumer = ContinuationConsumer(cont)
+ whenComplete(consumer)
+ cont.invokeOnCancellation {
+ // mayInterruptIfRunning is not used
+ (this as? CompletableFuture<T>)?.cancel(false)
+ consumer.cont = null // shall clear reference to continuation to aid GC
+ }
+ }
+}
+
+private class ContinuationConsumer<T>(
+ @Volatile @JvmField var cont: Continuation<T>?
+) : BiConsumer<T?, Throwable?> {
+ @Suppress("UNCHECKED_CAST")
+ override fun accept(result: T?, exception: Throwable?) {
+ val cont = this.cont ?: return // atomically read current value unless null
+ if (exception == null) {
+ // the future has completed normally
+ cont.resume(result as T)
+ } else {
+ // the future has completed with an exception, unwrap it to provide consistent view of .await() result and to propagate only original exception
+ cont.resumeWithException((exception as? CompletionException)?.cause ?: exception)
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/src/time/Time.kt b/integration/kotlinx-coroutines-jdk8/src/time/Time.kt
new file mode 100644
index 00000000..031ac61f
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/src/time/Time.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines.time
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
+import java.time.*
+import java.time.temporal.*
+
+/**
+ * "java.time" adapter method for [kotlinx.coroutines.delay].
+ */
+public suspend fun delay(duration: Duration) =
+ kotlinx.coroutines.delay(duration.coerceToMillis())
+
+/**
+ * "java.time" adapter method for [SelectBuilder.onTimeout].
+ */
+public fun <R> SelectBuilder<R>.onTimeout(duration: Duration, block: suspend () -> R) =
+ onTimeout(duration.coerceToMillis(), block)
+
+/**
+ * "java.time" adapter method for [kotlinx.coroutines.withTimeout].
+ */
+public suspend fun <T> withTimeout(duration: Duration, block: suspend CoroutineScope.() -> T): T =
+ kotlinx.coroutines.withTimeout(duration.coerceToMillis(), block)
+
+/**
+ * "java.time" adapter method for [kotlinx.coroutines.withTimeoutOrNull].
+ */
+public suspend fun <T> withTimeoutOrNull(duration: Duration, block: suspend CoroutineScope.() -> T): T? =
+ kotlinx.coroutines.withTimeoutOrNull(duration.coerceToMillis(), block)
+
+/**
+ * Coerces the given [Duration] to a millisecond delay.
+ * Negative values are coerced to zero, values that cannot
+ * be represented in milliseconds as long ("infinite" duration) are coerced to [Long.MAX_VALUE]
+ * and durations lesser than a millisecond are coerced to 1 millisecond.
+ *
+ * The rationale of coercion:
+ * 1) Too large durations typically indicate infinity and Long.MAX_VALUE is the
+ * best approximation of infinity we can provide.
+ * 2) Coercing too small durations to 1 instead of 0 is crucial for two patterns:
+ * - Programming with deadlines and delays
+ * - Non-suspending fast-paths (e.g. `withTimeout(1 nanosecond) { 42 }` should not throw)
+ */
+private fun Duration.coerceToMillis(): Long {
+ if (this <= Duration.ZERO) return 0
+ if (this <= ChronoUnit.MILLIS.duration) return 1
+
+ // Maximum scalar values of Duration.ofMillis(Long.MAX_VALUE)
+ val maxSeconds = 9223372036854775
+ val maxNanos = 807000000
+ return if (seconds < maxSeconds || seconds == maxSeconds && nano < maxNanos) toMillis()
+ else Long.MAX_VALUE
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/examples/CancelFuture-example.kt b/integration/kotlinx-coroutines-jdk8/test/examples/CancelFuture-example.kt
new file mode 100644
index 00000000..4595f762
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/examples/CancelFuture-example.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.examples
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.future.*
+
+
+fun main(args: Array<String>) {
+ val f = GlobalScope.future {
+ try {
+ log("Started f")
+ delay(500)
+ log("Slept 500 ms #1")
+ delay(500)
+ log("Slept 500 ms #2")
+ delay(500)
+ log("Slept 500 ms #3")
+ delay(500)
+ log("Slept 500 ms #4")
+ delay(500)
+ log("Slept 500 ms #5")
+ } catch(e: Exception) {
+ log("Aborting because of $e")
+ }
+ }
+ Thread.sleep(1200)
+ f.cancel(false)
+} \ No newline at end of file
diff --git a/integration/kotlinx-coroutines-jdk8/test/examples/ExplicitJob-example.kt b/integration/kotlinx-coroutines-jdk8/test/examples/ExplicitJob-example.kt
new file mode 100644
index 00000000..a331529a
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/examples/ExplicitJob-example.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.examples
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.future.*
+import java.util.concurrent.CancellationException
+
+fun main(args: Array<String>) {
+ val job = Job()
+ log("Starting futures f && g")
+ val f = GlobalScope.future(job) {
+ log("Started f")
+ delay(500)
+ log("f should not execute this line")
+ }
+ val g = GlobalScope.future(job) {
+ log("Started g")
+ try {
+ delay(500)
+ } finally {
+ log("g is executing finally!")
+ }
+ log("g should not execute this line")
+ }
+ log("Started futures f && g... will not wait -- cancel them!!!")
+ job.cancel()
+ check(f.isCancelled)
+ check(g.isCancelled)
+ log("f result = ${Try<Unit> { f.get() }}")
+ log("g result = ${Try<Unit> { g.get() }}")
+ Thread.sleep(1000L)
+ log("Nothing executed!")
+} \ No newline at end of file
diff --git a/integration/kotlinx-coroutines-jdk8/test/examples/ToFuture-example.kt b/integration/kotlinx-coroutines-jdk8/test/examples/ToFuture-example.kt
new file mode 100644
index 00000000..35fdf4f1
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/examples/ToFuture-example.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.examples
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.future.*
+import java.util.concurrent.*
+
+fun main(args: Array<String>) {
+ log("Started")
+ val deferred = GlobalScope.async {
+ log("Busy...")
+ delay(1000)
+ log("Done...")
+ 42
+ }
+ val future = deferred.asCompletableFuture()
+ log("Got ${future.get()}")
+}
+
+
diff --git a/integration/kotlinx-coroutines-jdk8/test/examples/Try.kt b/integration/kotlinx-coroutines-jdk8/test/examples/Try.kt
new file mode 100644
index 00000000..7f0d9888
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/examples/Try.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.examples
+
+import java.text.*
+import java.util.*
+
+public class Try<out T> private constructor(private val _value: Any?) {
+ private class Fail(val exception: Throwable) {
+ override fun toString(): String = "Failure[$exception]"
+ }
+ public companion object {
+ public operator fun <T> invoke(block: () -> T): Try<T> =
+ try { Success(block()) } catch(e: Throwable) { Failure<T>(e) }
+ public fun <T> Success(value: T) = Try<T>(value)
+ public fun <T> Failure(exception: Throwable) = Try<T>(Fail(exception))
+ }
+ @Suppress("UNCHECKED_CAST")
+ public val value: T get() = if (_value is Fail) throw _value.exception else _value as T
+ public val exception: Throwable? get() = (_value as? Fail)?.exception
+ override fun toString(): String = _value.toString()
+}
+
+fun log(msg: String) = println("${SimpleDateFormat("yyyyMMdd-HHmmss.sss").format(Date())} [${Thread.currentThread().name}] $msg")
diff --git a/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-1.kt b/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-1.kt
new file mode 100644
index 00000000..ed993910
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-1.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.examples
+
+import kotlinx.coroutines.future.await
+import kotlinx.coroutines.runBlocking
+import java.util.concurrent.CompletableFuture
+
+fun main(args: Array<String>) {
+ // Let's assume that we have a future coming from some 3rd party API
+ val future: CompletableFuture<Int> = CompletableFuture.supplyAsync {
+ Thread.sleep(1000L) // imitate some long-running computation, actually
+ 42
+ }
+ // now let's launch a coroutine and await for this future inside it
+ runBlocking {
+ println("We can do something else, while we are waiting for future...")
+ println("We've got ${future.await()} from the future!")
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-2.kt b/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-2.kt
new file mode 100644
index 00000000..0be80fc0
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-2.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.examples
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.future.*
+import java.util.concurrent.*
+
+// this function returns a CompletableFuture using Kotlin coroutines
+fun supplyTheAnswerAsync(): CompletableFuture<Int> = GlobalScope.future {
+ println("We might be doing some asynchronous IO here or something else...")
+ delay(1000) // just do a non-blocking delay
+ 42 // The answer!
+}
+
+fun main(args: Array<String>) {
+ // We can use `supplyTheAnswerAsync` just like any other future-supplier function
+ val future = supplyTheAnswerAsync()
+ println("The answer is ${future.get()}")
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-3.kt b/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-3.kt
new file mode 100644
index 00000000..e284ac16
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/examples/simple-example-3.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.examples
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.future.*
+import java.util.concurrent.*
+
+fun main(args: Array<String>) {
+ // this example shows how easy it is to perform multiple async operations with coroutines
+ val future = GlobalScope.future {
+ (1..5).map { // loops are no problem at all
+ startLongAsyncOperation(it).await() // suspend while the long method is running
+ }.joinToString("\n")
+ }
+ println("We have a long-running computation in background, let's wait for its result...")
+ println(future.get())
+}
+
+fun startLongAsyncOperation(num: Int): CompletableFuture<String> =
+ CompletableFuture.supplyAsync {
+ Thread.sleep(1000L) // imitate some long-running computation, actually
+ "$num" // and return a number converted to string
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/examples/withTimeout-example.kt b/integration/kotlinx-coroutines-jdk8/test/examples/withTimeout-example.kt
new file mode 100644
index 00000000..ef7c6b47
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/examples/withTimeout-example.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.examples
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.future.*
+
+fun main(args: Array<String>) {
+ fun slow(s: String) = GlobalScope.future {
+ delay(500L)
+ s
+ }
+ val f = GlobalScope.future {
+ log("Started f")
+ val a = slow("A").await()
+ log("a = $a")
+ withTimeout(1000L) {
+ val b = slow("B").await()
+ log("b = $b")
+ }
+ try {
+ withTimeout(750L) {
+ val c = slow("C").await()
+ log("c = $c")
+ val d = slow("D").await()
+ log("d = $d")
+ }
+ } catch (ex: CancellationException) {
+ log("timed out with $ex")
+ }
+ val e = slow("E").await()
+ log("e = $e")
+ "done"
+ }
+ log("f.get() = ${f.get()}")
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/future/AsFutureTest.kt b/integration/kotlinx-coroutines-jdk8/test/future/AsFutureTest.kt
new file mode 100644
index 00000000..72a1228b
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/future/AsFutureTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.future
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Assert.*
+import java.util.concurrent.*
+import java.util.concurrent.CancellationException
+
+class AsFutureTest : TestBase() {
+
+ @Test
+ fun testCompletedDeferredAsCompletableFuture() = runTest {
+ expect(1)
+ val deferred = async(start = CoroutineStart.UNDISPATCHED) {
+ expect(2) // completed right away
+ "OK"
+ }
+ expect(3)
+ val future = deferred.asCompletableFuture()
+ assertEquals("OK", future.await())
+ finish(4)
+ }
+
+ @Test
+ fun testCompletedJobAsCompletableFuture() = runTest {
+ val job = Job().apply { complete() }
+ val future = job.asCompletableFuture()
+ assertEquals(Unit, future.await())
+ }
+
+ @Test
+ fun testWaitForDeferredAsCompletableFuture() = runTest {
+ expect(1)
+ val deferred = async {
+ expect(3) // will complete later
+ "OK"
+ }
+ expect(2)
+ val future = deferred.asCompletableFuture()
+ assertEquals("OK", future.await()) // await yields main thread to deferred coroutine
+ finish(4)
+ }
+
+ @Test
+ fun testWaitForJobAsCompletableFuture() = runTest {
+ val job = Job()
+ val future = job.asCompletableFuture()
+ assertTrue(job.isActive)
+ job.complete()
+ assertFalse(job.isActive)
+ assertEquals(Unit, future.await())
+ }
+
+ @Test
+ fun testAsCompletableFutureThrowable() {
+ val deferred = GlobalScope.async<Unit> { throw OutOfMemoryError() }
+ val future = deferred.asCompletableFuture()
+ try {
+ expect(1)
+ future.get()
+ expectUnreached()
+ } catch (e: ExecutionException) {
+ assertTrue(future.isCompletedExceptionally)
+ assertTrue(e.cause is OutOfMemoryError)
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testJobAsCompletableFutureThrowable() {
+ val job = Job()
+ CompletableDeferred<Unit>(parent = job).apply { completeExceptionally(OutOfMemoryError()) }
+ val future = job.asCompletableFuture()
+ try {
+ expect(1)
+ future.get()
+ expectUnreached()
+ } catch (e: ExecutionException) {
+ assertTrue(future.isCompletedExceptionally)
+ assertTrue(e.cause is OutOfMemoryError)
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testJobAsCompletableFutureCancellation() {
+ val job = Job()
+ val future = job.asCompletableFuture()
+ job.cancel()
+ try {
+ expect(1)
+ future.get()
+ expectUnreached()
+ } catch (e: CancellationException) {
+ assertTrue(future.isCompletedExceptionally)
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testJobCancellation() {
+ val job = Job()
+ val future = job.asCompletableFuture()
+ future.cancel(true)
+ assertTrue(job.isCancelled)
+ assertTrue(job.isCompleted)
+ assertFalse(job.isActive)
+ }
+
+ @Test
+ fun testDeferredCancellation() {
+ val deferred = CompletableDeferred<Int>()
+ val future = deferred.asCompletableFuture()
+ future.cancel(true)
+ assertTrue(deferred.isCancelled)
+ assertTrue(deferred.isCompleted)
+ assertFalse(deferred.isActive)
+ assertTrue(deferred.getCompletionExceptionOrNull() is CancellationException)
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/future/FutureExceptionsTest.kt b/integration/kotlinx-coroutines-jdk8/test/future/FutureExceptionsTest.kt
new file mode 100644
index 00000000..86b60e5f
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/future/FutureExceptionsTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.future
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Assert.*
+import java.io.*
+import java.util.concurrent.*
+
+class FutureExceptionsTest : TestBase() {
+
+ @Test
+ fun testAwait() {
+ testException(IOException(), { it is IOException })
+ }
+
+ @Test
+ fun testAwaitChained() {
+ testException(IOException(), { it is IOException }, { f -> f.thenApply { it + 1 } })
+ }
+
+ @Test
+ fun testAwaitDeepChain() {
+ testException(IOException(), { it is IOException },
+ { f -> f
+ .thenApply { it + 1 }
+ .thenApply { it + 2 } })
+ }
+
+ @Test
+ fun testAwaitCompletionException() {
+ testException(CompletionException("test", IOException()), { it is IOException })
+ }
+
+ @Test
+ fun testAwaitChainedCompletionException() {
+ testException(CompletionException("test", IOException()), { it is IOException }, { f -> f.thenApply { it + 1 } })
+ }
+
+ @Test
+ fun testAwaitTestException() {
+ testException(TestException(), { it is TestException })
+ }
+
+ @Test
+ fun testAwaitChainedTestException() {
+ testException(TestException(), { it is TestException }, { f -> f.thenApply { it + 1 } })
+ }
+
+ private fun testException(
+ exception: Throwable,
+ expected: ((Throwable) -> Boolean),
+ transformer: (CompletableFuture<Int>) -> CompletableFuture<Int> = { it }
+ ) {
+
+ // Fast path
+ runTest {
+ val future = CompletableFuture<Int>()
+ val chained = transformer(future)
+ future.completeExceptionally(exception)
+ try {
+ chained.await()
+ } catch (e: Throwable) {
+ assertTrue(expected(e))
+ }
+ }
+
+ // Slow path
+ runTest {
+ val future = CompletableFuture<Int>()
+ val chained = transformer(future)
+
+ launch {
+ future.completeExceptionally(exception)
+ }
+
+ try {
+ chained.await()
+ } catch (e: Throwable) {
+ assertTrue(expected(e))
+ }
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt b/integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt
new file mode 100644
index 00000000..4649645e
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.future
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import java.util.concurrent.locks.*
+import java.util.function.*
+import kotlin.concurrent.*
+import kotlin.coroutines.*
+import kotlin.reflect.*
+
+class FutureTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("ForkJoinPool.commonPool-worker-")
+ }
+
+ @Test
+ fun testSimpleAwait() {
+ val future = GlobalScope.future {
+ CompletableFuture.supplyAsync {
+ "O"
+ }.await() + "K"
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testCompletedFuture() {
+ val toAwait = CompletableFuture<String>()
+ toAwait.complete("O")
+ val future = GlobalScope.future {
+ toAwait.await() + "K"
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testCompletedCompletionStage() {
+ val completable = CompletableFuture<String>()
+ completable.complete("O")
+ val toAwait: CompletionStage<String> = completable
+ val future = GlobalScope.future {
+ toAwait.await() + "K"
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testWaitForFuture() {
+ val toAwait = CompletableFuture<String>()
+ val future = GlobalScope.future {
+ toAwait.await() + "K"
+ }
+ assertFalse(future.isDone)
+ toAwait.complete("O")
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testWaitForCompletionStage() {
+ val completable = CompletableFuture<String>()
+ val toAwait: CompletionStage<String> = completable
+ val future = GlobalScope.future {
+ toAwait.await() + "K"
+ }
+ assertFalse(future.isDone)
+ completable.complete("O")
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testCompletedFutureExceptionally() {
+ val toAwait = CompletableFuture<String>()
+ toAwait.completeExceptionally(TestException("O"))
+ val future = GlobalScope.future {
+ try {
+ toAwait.await()
+ } catch (e: TestException) {
+ e.message!!
+ } + "K"
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ // Test fast-path of CompletionStage.await() extension
+ fun testCompletedCompletionStageExceptionally() {
+ val completable = CompletableFuture<String>()
+ val toAwait: CompletionStage<String> = completable
+ completable.completeExceptionally(TestException("O"))
+ val future = GlobalScope.future {
+ try {
+ toAwait.await()
+ } catch (e: TestException) {
+ e.message!!
+ } + "K"
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ // Test slow-path of CompletionStage.await() extension
+ fun testWaitForFutureWithException() = runTest {
+ expect(1)
+ val toAwait = CompletableFuture<String>()
+ val future = future(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ toAwait.await() // will suspend (slow path)
+ } catch (e: TestException) {
+ expect(4)
+ e.message!!
+ } + "K"
+ }
+ expect(3)
+ assertFalse(future.isDone)
+ toAwait.completeExceptionally(TestException("O"))
+ yield() // to future coroutine
+ assertThat(future.get(), IsEqual("OK"))
+ finish(5)
+ }
+
+ @Test
+ fun testWaitForCompletionStageWithException() {
+ val completable = CompletableFuture<String>()
+ val toAwait: CompletionStage<String> = completable
+ val future = GlobalScope.future {
+ try {
+ toAwait.await()
+ } catch (e: TestException) {
+ e.message!!
+ } + "K"
+ }
+ assertFalse(future.isDone)
+ completable.completeExceptionally(TestException("O"))
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testExceptionInsideCoroutine() {
+ val future = GlobalScope.future {
+ if (CompletableFuture.supplyAsync { true }.await()) {
+ throw IllegalStateException("OK")
+ }
+ "fail"
+ }
+ try {
+ future.get()
+ fail("'get' should've throw an exception")
+ } catch (e: ExecutionException) {
+ assertTrue(e.cause is IllegalStateException)
+ assertThat(e.cause!!.message, IsEqual("OK"))
+ }
+ }
+
+ @Test
+ fun testCancellableAwaitFuture() = runBlocking {
+ expect(1)
+ val toAwait = CompletableFuture<String>()
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ toAwait.await() // suspends
+ } catch (e: CancellationException) {
+ expect(5) // should throw cancellation exception
+ throw e
+ }
+ }
+ expect(3)
+ job.cancel() // cancel the job
+ toAwait.complete("fail") // too late, the waiting job was already cancelled
+ expect(4) // job processing of cancellation was scheduled, not executed yet
+ yield() // yield main thread to job
+ finish(6)
+ }
+
+ @Test
+ fun testContinuationWrapped() {
+ val depth = AtomicInteger()
+ val future = GlobalScope.future(wrapContinuation {
+ depth.andIncrement
+ it()
+ depth.andDecrement
+ }) {
+ assertEquals("Part before first suspension must be wrapped", 1, depth.get())
+ val result =
+ CompletableFuture.supplyAsync {
+ while (depth.get() > 0);
+ assertEquals("Part inside suspension point should not be wrapped", 0, depth.get())
+ "OK"
+ }.await()
+ assertEquals("Part after first suspension should be wrapped", 1, depth.get())
+ CompletableFuture.supplyAsync {
+ while (depth.get() > 0);
+ assertEquals("Part inside suspension point should not be wrapped", 0, depth.get())
+ "ignored"
+ }.await()
+ result
+ }
+ assertThat(future.get(), IsEqual("OK"))
+ }
+
+ @Test
+ fun testCompletableFutureStageAsDeferred() = runBlocking {
+ val lock = ReentrantLock().apply { lock() }
+
+ val deferred: Deferred<Int> = CompletableFuture.supplyAsync {
+ lock.withLock { 42 }
+ }.asDeferred()
+
+ assertFalse(deferred.isCompleted)
+ lock.unlock()
+
+ assertEquals(42, deferred.await())
+ assertTrue(deferred.isCompleted)
+ }
+
+ @Test
+ fun testCompletedFutureAsDeferred() = runBlocking {
+ val deferred: Deferred<Int> = CompletableFuture.completedFuture(42).asDeferred()
+ assertEquals(42, deferred.await())
+ }
+
+ @Test
+ fun testFailedFutureAsDeferred() = runBlocking {
+ val future = CompletableFuture<Int>().apply {
+ completeExceptionally(TestException("something went wrong"))
+ }
+ val deferred = future.asDeferred()
+
+ assertTrue(deferred.isCancelled)
+ 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: Throwable) {
+ assertTrue(e is TestException)
+ assertEquals("something went wrong", e.message)
+ }
+ }
+
+ @Test
+ fun testCompletableFutureWithExceptionAsDeferred() = runBlocking {
+ val lock = ReentrantLock().apply { lock() }
+
+ val deferred: Deferred<Int> = CompletableFuture.supplyAsync {
+ lock.withLock { throw TestException("something went wrong") }
+ }.asDeferred()
+
+ assertFalse(deferred.isCompleted)
+ lock.unlock()
+ try {
+ deferred.await()
+ fail("deferred.await() should throw an exception")
+ } catch (e: TestException) {
+ assertTrue(deferred.isCancelled)
+ assertEquals("something went wrong", e.message)
+ }
+ }
+
+ private val threadLocal = ThreadLocal<String>()
+
+ @Test
+ fun testApiBridge() = runTest {
+ val result = newSingleThreadContext("ctx").use {
+ val future = CompletableFuture.supplyAsync(Supplier { threadLocal.set("value") }, it.executor)
+ val job = async(it) {
+ future.await()
+ threadLocal.get()
+ }
+
+ job.await()
+ }
+
+ assertEquals("value", result)
+ }
+
+ @Test
+ fun testFutureCancellation() = runTest {
+ val future = awaitFutureWithCancel(true)
+ assertTrue(future.isCompletedExceptionally)
+ assertFailsWith<CancellationException> { future.get() }
+ finish(4)
+ }
+
+ @Test
+ fun testNoFutureCancellation() = runTest {
+ val future = awaitFutureWithCancel(false)
+ assertFalse(future.isCompletedExceptionally)
+ assertEquals(239, future.get())
+ finish(4)
+ }
+
+ private suspend fun CoroutineScope.awaitFutureWithCancel(cancellable: Boolean): CompletableFuture<Int> {
+ val latch = CountDownLatch(1)
+ val future = CompletableFuture.supplyAsync {
+ latch.await()
+ 239
+ }
+
+ val deferred = async {
+ expect(2)
+ if (cancellable) future.await()
+ else future.asDeferred().await()
+ }
+ expect(1)
+ yield()
+ deferred.cancel()
+ expect(3)
+ latch.countDown()
+ return future
+ }
+
+ @Test
+ fun testStructuredException() = runTest(
+ expected = { it is TestException } // exception propagates to parent with structured concurrency
+ ) {
+ val result = future<Int>(Dispatchers.Unconfined) {
+ throw TestException("FAIL")
+ }
+ result.checkFutureException<TestException>()
+ }
+
+ @Test
+ fun testChildException() = runTest(
+ expected = { it is TestException } // exception propagates to parent with structured concurrency
+ ) {
+ val result = future(Dispatchers.Unconfined) {
+ // child crashes
+ launch { throw TestException("FAIL") }
+ 42
+ }
+ result.checkFutureException<TestException>()
+ }
+
+ @Test
+ fun testExceptionAggregation() = runTest(
+ expected = { it is TestException } // exception propagates to parent with structured concurrency
+ ) {
+ val result = future(Dispatchers.Unconfined) {
+ // child crashes
+ launch(start = CoroutineStart.ATOMIC) { throw TestException1("FAIL") }
+ launch(start = CoroutineStart.ATOMIC) { throw TestException2("FAIL") }
+ throw TestException()
+ }
+ result.checkFutureException<TestException>(TestException1::class, TestException2::class)
+ finish(1)
+ }
+
+ @Test
+ fun testExternalCompletion() = runTest {
+ expect(1)
+ val result = future(Dispatchers.Unconfined) {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(2)
+ }
+ }
+
+ result.complete(Unit)
+ finish(3)
+ }
+
+ @Test
+ fun testExceptionOnExternalCompletion() = runTest(
+ expected = { it is TestException } // exception propagates to parent with structured concurrency
+ ) {
+ expect(1)
+ val result = future(Dispatchers.Unconfined) {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(2)
+ throw TestException()
+ }
+ }
+ result.complete(Unit)
+ finish(3)
+ }
+
+ @Test
+ fun testUnhandledExceptionOnExternalCompletion() = runTest(
+ unhandled = listOf(
+ { it -> it is TestException } // exception is unhandled because there is no parent
+ )
+ ) {
+ 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 TestException() // this exception cannot be handled
+ }
+ }
+ result.complete(Unit)
+ finish(3)
+ }
+
+ /**
+ * See [https://github.com/Kotlin/kotlinx.coroutines/issues/892]
+ */
+ @Test
+ fun testTimeoutCancellationFailRace() {
+ repeat(10 * stressTestMultiplier) {
+ runBlocking {
+ withTimeoutOrNull(10) {
+ while (true) {
+ var caught = false
+ try {
+ CompletableFuture.supplyAsync {
+ throw TestException()
+ }.await()
+ } catch (ignored: TestException) {
+ caught = true
+ }
+ assertTrue(caught) // should have caught TestException or timed out
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests that both [CompletionStage.await] and [CompletionStage.asDeferred] consistently unwrap
+ * [CompletionException] both in their slow and fast paths.
+ * See [issue #1479](https://github.com/Kotlin/kotlinx.coroutines/issues/1479).
+ */
+ @Test
+ fun testConsistentExceptionUnwrapping() = runTest {
+ expect(1)
+ // Check the fast path
+ val fFast = CompletableFuture.supplyAsync {
+ expect(2)
+ throw TestException()
+ }
+ fFast.checkFutureException<TestException>() // wait until it completes
+ // Fast path in await and asDeferred.await() shall produce TestException
+ expect(3)
+ val dFast = fFast.asDeferred()
+ assertFailsWith<TestException> { fFast.await() }
+ assertFailsWith<TestException> { dFast.await() }
+ // Same test, but future has not completed yet, check the slow path
+ expect(4)
+ val barrier = CyclicBarrier(2)
+ val fSlow = CompletableFuture.supplyAsync {
+ barrier.await()
+ expect(6)
+ throw TestException()
+ }
+ val dSlow = fSlow.asDeferred()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(5)
+ // Slow path on await shall produce TestException, too
+ assertFailsWith<TestException> { fSlow.await() } // will suspend here
+ assertFailsWith<TestException> { dSlow.await() }
+ finish(7)
+ }
+ barrier.await()
+ fSlow.checkFutureException<TestException>() // now wait until it completes
+ }
+
+ private inline fun <reified T: Throwable> CompletableFuture<*>.checkFutureException(vararg suppressed: KClass<out Throwable>) {
+ val e = assertFailsWith<ExecutionException> { get() }
+ val cause = e.cause!!
+ assertTrue(cause is T)
+ for ((index, clazz) in suppressed.withIndex()) {
+ assertTrue(clazz.isInstance(cause.suppressed[index]))
+ }
+ }
+
+ private fun wrapContinuation(wrapper: (() -> Unit) -> Unit): CoroutineDispatcher = object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ wrapper {
+ block.run()
+ }
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt b/integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt
new file mode 100644
index 00000000..9ab0ccf9
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.time
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
+import org.junit.Test
+import java.time.*
+import java.time.temporal.*
+import kotlin.test.*
+
+class DurationOverflowTest : TestBase() {
+
+ private val durations = ChronoUnit.values().map { it.duration }
+
+ @Test
+ fun testDelay() = runTest {
+ var counter = 0
+ for (duration in durations) {
+ expect(++counter)
+ delay(duration.negated()) // Instant bail out from negative values
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(++counter)
+ delay(duration)
+ }.cancelAndJoin()
+ expect(++counter)
+ }
+
+ finish(++counter)
+ }
+
+ @Test
+ fun testOnTimeout() = runTest {
+ for (duration in durations) {
+ // Does not crash on overflows
+ select<Unit> {
+ onTimeout(duration) {}
+ onTimeout(duration.negated()) {}
+ }
+ }
+ }
+
+ @Test
+ fun testWithTimeout() = runTest {
+ for (duration in durations) {
+ withTimeout(duration) {}
+ }
+ }
+
+ @Test
+ fun testWithTimeoutOrNull() = runTest {
+ for (duration in durations) {
+ withTimeoutOrNull(duration) {}
+ }
+ }
+
+ @Test
+ fun testWithTimeoutOrNullNegativeDuration() = runTest {
+ val result = withTimeoutOrNull(Duration.ofSeconds(1).negated()) {
+ 1
+ }
+
+ assertNull(result)
+ }
+
+ @Test
+ fun testZeroDurationWithTimeout() = runTest {
+ assertFailsWith<TimeoutCancellationException> { withTimeout(0L) {} }
+ assertFailsWith<TimeoutCancellationException> { withTimeout(Duration.ZERO) {} }
+ }
+
+ @Test
+ fun testZeroDurationWithTimeoutOrNull() = runTest {
+ assertNull(withTimeoutOrNull(0L) {})
+ assertNull(withTimeoutOrNull(Duration.ZERO) {})
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/time/WithTimeoutTest.kt b/integration/kotlinx-coroutines-jdk8/test/time/WithTimeoutTest.kt
new file mode 100644
index 00000000..d33eaac1
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/time/WithTimeoutTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.time
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import java.time.*
+import kotlin.test.*
+
+class WithTimeoutTest : TestBase() {
+
+ @Test
+ fun testWithTimeout() = runTest {
+ expect(1)
+ val result = withTimeout(Duration.ofMillis(10_000)) {
+ expect(2)
+ delay(Duration.ofNanos(1))
+ expect(3)
+ 42
+ }
+
+ assertEquals(42, result)
+ finish(4)
+ }
+
+ @Test
+ fun testWithTimeoutOrNull() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(Duration.ofMillis(10_000)) {
+ expect(2)
+ delay(Duration.ofNanos(1))
+ expect(3)
+ 42
+ }
+
+ assertEquals(42, result)
+ finish(4)
+ }
+
+ @Test
+ fun testWithTimeoutOrNullExceeded() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(Duration.ofMillis(3)) {
+ expect(2)
+ delay(Duration.ofSeconds(Long.MAX_VALUE))
+ expectUnreached()
+ }
+
+ assertNull(result)
+ finish(3)
+ }
+
+ @Test
+ fun testWithTimeoutExceeded() = runTest {
+ expect(1)
+ try {
+ withTimeout(Duration.ofMillis(3)) {
+ expect(2)
+ delay(Duration.ofSeconds(Long.MAX_VALUE))
+ expectUnreached()
+ }
+ } catch (e: TimeoutCancellationException) {
+ finish(3)
+ }
+ }
+} \ No newline at end of file
diff --git a/integration/kotlinx-coroutines-play-services/README.md b/integration/kotlinx-coroutines-play-services/README.md
new file mode 100644
index 00000000..4ee6bf42
--- /dev/null
+++ b/integration/kotlinx-coroutines-play-services/README.md
@@ -0,0 +1,29 @@
+# Module kotlinx-coroutines-play-services
+
+Integration with Google Play Services [Tasks API](https://developers.google.com/android/guides/tasks).
+
+Extension functions:
+
+| **Name** | **Description**
+| -------- | ---------------
+| [Task.await][await] | Awaits for completion of the Task (cancellable)
+| [Deferred.asTask][asTask] | Converts a deferred value to a Task
+
+## Example
+
+Using Firebase APIs becomes simple:
+
+```kotlin
+FirebaseAuth.getInstance().signInAnonymously().await()
+val snapshot = try {
+ FirebaseFirestore.getInstance().document("users/$id").get().await() // Cancellable await
+} catch (e: FirebaseFirestoreException) {
+ // Handle exception
+ return@async
+}
+
+// Do stuff
+```
+
+[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/build.gradle b/integration/kotlinx-coroutines-play-services/build.gradle
new file mode 100644
index 00000000..61201fae
--- /dev/null
+++ b/integration/kotlinx-coroutines-play-services/build.gradle
@@ -0,0 +1,84 @@
+import java.nio.file.Files
+import java.nio.file.NoSuchFileException
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+ext.tasks_version = '16.0.1'
+
+def attr = Attribute.of("artifactType", String.class)
+configurations {
+ aar {
+ attributes { attribute(attr, ArtifactTypeDefinition.JAR_TYPE) }
+ sourceSets.main.compileClasspath += it
+ sourceSets.test.compileClasspath += it
+ sourceSets.test.runtimeClasspath += it
+ }
+}
+
+dependencies {
+ registerTransform {
+ from.attribute(attr, "aar")
+ to.attribute(attr, "jar")
+ artifactTransform(ExtractJars.class)
+ }
+
+ aar("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()
+ }
+
+ afterEvaluate {
+ classpath += project.configurations.aar.files
+ }
+}
+
+class ExtractJars extends ArtifactTransform {
+ @Override
+ List<File> transform(File input) {
+ unzip(input)
+
+ List<File> jars = new ArrayList<>()
+ outputDirectory.traverse(nameFilter: ~/.*\.jar/) { jars += it }
+
+ return jars
+ }
+
+ private void unzip(File zipFile) {
+ ZipFile zip
+ try {
+ zip = new ZipFile(zipFile)
+ for (entry in zip.entries()) {
+ unzipEntryTo(zip, entry)
+ }
+ } finally {
+ if (zip != null) zip.close()
+ }
+ }
+
+ private void unzipEntryTo(ZipFile zip, ZipEntry entry) {
+ File output = new File(outputDirectory, entry.name)
+ if (entry.isDirectory()) {
+ output.mkdirs()
+ } else {
+ InputStream stream
+ try {
+ stream = zip.getInputStream(entry)
+ Files.copy(stream, output.toPath())
+ } catch (NoSuchFileException ignored) {
+ } finally {
+ if (stream != null) stream.close()
+ }
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-play-services/package.list b/integration/kotlinx-coroutines-play-services/package.list
new file mode 100644
index 00000000..4b22ba14
--- /dev/null
+++ b/integration/kotlinx-coroutines-play-services/package.list
@@ -0,0 +1 @@
+com.google.android.gms.tasks \ No newline at end of file
diff --git a/integration/kotlinx-coroutines-play-services/src/Tasks.kt b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
new file mode 100644
index 00000000..4952daa7
--- /dev/null
+++ b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016-2018 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 kotlin.coroutines.*
+
+/**
+ * Converts this deferred to the instance of [Task].
+ * If deferred is cancelled then resulting task will be cancelled as well.
+ */
+public fun <T> Deferred<T>.asTask(): Task<T> {
+ val cancellation = CancellationTokenSource()
+ val source = TaskCompletionSource<T>(cancellation.token)
+
+ invokeOnCompletion callback@{
+ if (it is CancellationException) {
+ cancellation.cancel()
+ return@callback
+ }
+
+ val t = getCompletionExceptionOrNull()
+ if (t == null) {
+ source.setResult(getCompleted())
+ } else {
+ source.setException(t as? Exception ?: RuntimeExecutionException(t))
+ }
+ }
+
+ return source.task
+}
+
+/**
+ * Converts this task to an instance of [Deferred].
+ * If task is cancelled then resulting deferred will be cancelled as well.
+ */
+public fun <T> Task<T>.asDeferred(): Deferred<T> {
+ if (isComplete) {
+ val e = exception
+ return if (e == null) {
+ @Suppress("UNCHECKED_CAST")
+ CompletableDeferred<T>().apply { if (isCanceled) cancel() else complete(result as T) }
+ } else {
+ CompletableDeferred<T>().apply { 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)
+ }
+ }
+ return result
+}
+
+/**
+ * Awaits for 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].
+ */
+public suspend fun <T> Task<T>.await(): T {
+ // fast path
+ if (isComplete) {
+ val e = exception
+ return if (e == null) {
+ if (isCanceled) {
+ throw CancellationException("Task $this was cancelled normally.")
+ } else {
+ @Suppress("UNCHECKED_CAST")
+ result as T
+ }
+ } else {
+ throw e
+ }
+ }
+
+ return suspendCancellableCoroutine { cont ->
+ addOnCompleteListener {
+ val e = exception
+ if (e == null) {
+ @Suppress("UNCHECKED_CAST")
+ if (isCanceled) cont.cancel() else cont.resume(result as T)
+ } else {
+ cont.resumeWithException(e)
+ }
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt b/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
new file mode 100644
index 00000000..6026ffd7
--- /dev/null
+++ b/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
@@ -0,0 +1,18 @@
+package android.os
+
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+
+class Handler(val looper: Looper) {
+ fun post(r: Runnable): Boolean {
+ GlobalScope.launch { r.run() }
+ return true
+ }
+}
+
+class Looper {
+ companion object {
+ @JvmStatic
+ fun getMainLooper() = Looper()
+ }
+}
diff --git a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
new file mode 100644
index 00000000..b87a2954
--- /dev/null
+++ b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.tasks
+
+import com.google.android.gms.tasks.*
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.locks.*
+import kotlin.concurrent.*
+import kotlin.test.*
+
+class TaskTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("ForkJoinPool.commonPool-worker-")
+ }
+
+ @Test
+ fun testCompletedDeferredAsTask() = runTest {
+ expect(1)
+ val deferred = async(start = CoroutineStart.UNDISPATCHED) {
+ expect(2) // Completed immediately
+ "OK"
+ }
+ expect(3)
+ val task = deferred.asTask()
+ assertEquals("OK", task.await())
+ finish(4)
+ }
+
+ @Test
+ fun testDeferredAsTask() = runTest {
+ expect(1)
+ val deferred = async {
+ expect(3) // Completed later
+ "OK"
+ }
+ expect(2)
+ val task = deferred.asTask()
+ assertEquals("OK", task.await())
+ finish(4)
+ }
+
+ @Test
+ fun testCancelledAsTask() {
+ val deferred = GlobalScope.async {
+ delay(100)
+ }.apply { cancel() }
+
+ val task = deferred.asTask()
+ try {
+ runTest { task.await() }
+ } catch (e: Exception) {
+ assertTrue(e is CancellationException)
+ assertTrue(task.isCanceled)
+ }
+ }
+
+ @Test
+ fun testThrowingAsTask() {
+ val deferred = GlobalScope.async {
+ throw TestException("Fail")
+ }
+
+ val task = deferred.asTask()
+ runTest(expected = { it is TestException }) {
+ task.await()
+ }
+ }
+
+ @Test
+ fun testStateAsTask() = runTest {
+ val lock = ReentrantLock().apply { lock() }
+
+ val deferred: Deferred<Int> = Tasks.call {
+ lock.withLock { 42 }
+ }.asDeferred()
+
+ assertFalse(deferred.isCompleted)
+ lock.unlock()
+
+ assertEquals(42, deferred.await())
+ assertTrue(deferred.isCompleted)
+ }
+
+ @Test
+ fun testTaskAsDeferred() = runTest {
+ val deferred = Tasks.forResult(42).asDeferred()
+ assertEquals(42, deferred.await())
+ }
+
+ @Test
+ fun testNullResultTaskAsDeferred() = runTest {
+ assertNull(Tasks.forResult(null).asDeferred().await())
+ }
+
+ @Test
+ fun testCancelledTaskAsDeferred() = runTest {
+ val deferred = Tasks.forCanceled<Int>().asDeferred()
+
+ assertTrue(deferred.isCancelled)
+ try {
+ deferred.await()
+ fail("deferred.await() should be cancelled")
+ } catch (e: Exception) {
+ assertTrue(e is CancellationException)
+ }
+ }
+
+ @Test
+ fun testFailedTaskAsDeferred() = runTest {
+ val deferred = Tasks.forException<Int>(TestException("something went wrong")).asDeferred()
+
+ 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)
+ }
+ }
+
+ @Test
+ fun testFailingTaskAsDeferred() = runTest {
+ val lock = ReentrantLock().apply { lock() }
+
+ val deferred: Deferred<Int> = Tasks.call {
+ lock.withLock { throw TestException("something went wrong") }
+ }.asDeferred()
+
+ 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
+ }
+ }
+
+ class TestException(message: String) : Exception(message)
+}
diff --git a/integration/kotlinx-coroutines-slf4j/README.md b/integration/kotlinx-coroutines-slf4j/README.md
new file mode 100644
index 00000000..ee5fb320
--- /dev/null
+++ b/integration/kotlinx-coroutines-slf4j/README.md
@@ -0,0 +1,24 @@
+# Module kotlinx-coroutines-slf4j
+
+Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html).
+
+## Example
+
+Add [MDCContext] to the coroutine context so that the SLF4J MDC context is captured and passed into the coroutine.
+
+```kotlin
+MDC.put("kotlin", "rocks") // put a value into the MDC context
+
+launch(MDCContext()) {
+ logger.info { "..." } // the MDC context will contain the mapping here
+}
+```
+
+# Package kotlinx.coroutines.slf4j
+
+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/build.gradle b/integration/kotlinx-coroutines-slf4j/build.gradle
new file mode 100644
index 00000000..161a0b84
--- /dev/null
+++ b/integration/kotlinx-coroutines-slf4j/build.gradle
@@ -0,0 +1,13 @@
+dependencies {
+ compile 'org.slf4j:slf4j-api:1.7.25'
+ testCompile 'io.github.microutils:kotlin-logging:1.5.4'
+ testRuntime 'ch.qos.logback:logback-classic:1.2.3'
+ testRuntime 'ch.qos.logback:logback-core:1.2.3'
+}
+
+tasks.withType(dokka.getClass()) {
+ externalDocumentationLink {
+ packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
+ url = new URL("https://www.slf4j.org/apidocs/")
+ }
+} \ No newline at end of file
diff --git a/integration/kotlinx-coroutines-slf4j/package.list b/integration/kotlinx-coroutines-slf4j/package.list
new file mode 100644
index 00000000..bfea07f5
--- /dev/null
+++ b/integration/kotlinx-coroutines-slf4j/package.list
@@ -0,0 +1,21 @@
+org.apache.commons.logging
+org.apache.commons.logging.impl
+org.apache.log4j
+org.apache.log4j.helpers
+org.apache.log4j.spi
+org.apache.log4j.xml
+org.slf4j
+org.slf4j.agent
+org.slf4j.bridge
+org.slf4j.cal10n
+org.slf4j.event
+org.slf4j.ext
+org.slf4j.helpers
+org.slf4j.instrumentation
+org.slf4j.jul
+org.slf4j.log4j12
+org.slf4j.nop
+org.slf4j.osgi.logservice.impl
+org.slf4j.profiler
+org.slf4j.simple
+org.slf4j.spi
diff --git a/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
new file mode 100644
index 00000000..c9b1dd9a
--- /dev/null
+++ b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.slf4j
+
+import kotlinx.coroutines.*
+import org.slf4j.MDC
+import kotlin.coroutines.AbstractCoroutineContextElement
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * The value of [MDC] context map.
+ * See [MDC.getCopyOfContextMap].
+ */
+public typealias MDCContextMap = Map<String, String>?
+
+/**
+ * [MDC] context element for [CoroutineContext].
+ *
+ * Example:
+ *
+ * ```
+ * MDC.put("kotlin", "rocks") // Put a value into the MDC context
+ *
+ * launch(MDCContext()) {
+ * logger.info { "..." } // The MDC context contains the mapping here
+ * }
+ * ```
+ *
+ * Note that you cannot update MDC context from inside of the coroutine simply
+ * using [MDC.put]. These updates are going to be lost on the next suspension and
+ * reinstalled to the MDC context that was captured or explicitly specified in
+ * [contextMap] when this object was created on the next resumption.
+ * Use `withContext(MDCContext()) { ... }` to capture updated map of MDC keys and values
+ * for the specified block of code.
+ *
+ * @param contextMap the value of [MDC] context map.
+ * Default value is the copy of the current thread's context map that is acquired via
+ * [MDC.getCopyOfContextMap].
+ */
+public class MDCContext(
+ /**
+ * The value of [MDC] context map.
+ */
+ public val contextMap: MDCContextMap = MDC.getCopyOfContextMap()
+) : ThreadContextElement<MDCContextMap>, AbstractCoroutineContextElement(Key) {
+ /**
+ * Key of [MDCContext] in [CoroutineContext].
+ */
+ companion object Key : CoroutineContext.Key<MDCContext>
+
+ /** @suppress */
+ override fun updateThreadContext(context: CoroutineContext): MDCContextMap {
+ val oldState = MDC.getCopyOfContextMap()
+ setCurrent(contextMap)
+ return oldState
+ }
+
+ /** @suppress */
+ override fun restoreThreadContext(context: CoroutineContext, oldState: MDCContextMap) {
+ setCurrent(oldState)
+ }
+
+ private fun setCurrent(contextMap: MDCContextMap) {
+ if (contextMap == null) {
+ MDC.clear()
+ } else {
+ MDC.setContextMap(contextMap)
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-slf4j/test-resources/logback-test.xml b/integration/kotlinx-coroutines-slf4j/test-resources/logback-test.xml
new file mode 100644
index 00000000..80510114
--- /dev/null
+++ b/integration/kotlinx-coroutines-slf4j/test-resources/logback-test.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration debug="false">
+
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <layout>
+ <Pattern>%X{first} %X{last} - %m%n</Pattern>
+ </layout>
+ </appender>
+
+ <root level="DEBUG">
+ <appender-ref ref="CONSOLE" />
+ </root>
+</configuration>
+
+
diff --git a/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
new file mode 100644
index 00000000..f3ed957b
--- /dev/null
+++ b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.slf4j
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import org.slf4j.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class MDCContextTest : TestBase() {
+ @Before
+ fun setUp() {
+ MDC.clear()
+ }
+
+ @After
+ fun tearDown() {
+ MDC.clear()
+ }
+
+ @Test
+ fun testContextIsNotPassedByDefaultBetweenCoroutines() = runTest {
+ expect(1)
+ MDC.put("myKey", "myValue")
+ // Standalone launch
+ GlobalScope.launch {
+ assertEquals(null, MDC.get("myKey"))
+ expect(2)
+ }.join()
+ finish(3)
+ }
+
+ @Test
+ fun testContextCanBePassedBetweenCoroutines() = runTest {
+ expect(1)
+ MDC.put("myKey", "myValue")
+ // Scoped launch with MDCContext element
+ launch(MDCContext()) {
+ assertEquals("myValue", MDC.get("myKey"))
+ expect(2)
+ }.join()
+
+ finish(3)
+ }
+
+ @Test
+ fun testContextInheritance() = runTest {
+ expect(1)
+ MDC.put("myKey", "myValue")
+ withContext(MDCContext()) {
+ MDC.put("myKey", "myValue2")
+ // Scoped launch with inherited MDContext element
+ launch(Dispatchers.Default) {
+ assertEquals("myValue", MDC.get("myKey"))
+ expect(2)
+ }.join()
+
+ finish(3)
+ }
+ assertEquals("myValue", MDC.get("myKey"))
+ }
+
+ @Test
+ fun testContextPassedWhileOnMainThread() {
+ MDC.put("myKey", "myValue")
+ // No MDCContext element
+ runBlocking {
+ assertEquals("myValue", MDC.get("myKey"))
+ }
+ }
+
+ @Test
+ fun testContextCanBePassedWhileOnMainThread() {
+ MDC.put("myKey", "myValue")
+ runBlocking(MDCContext()) {
+ assertEquals("myValue", MDC.get("myKey"))
+ }
+ }
+
+ @Test
+ fun testContextNeededWithOtherContext() {
+ MDC.put("myKey", "myValue")
+ runBlocking(MDCContext()) {
+ assertEquals("myValue", MDC.get("myKey"))
+ }
+ }
+
+ @Test
+ fun testContextMayBeEmpty() {
+ runBlocking(MDCContext()) {
+ assertEquals(null, MDC.get("myKey"))
+ }
+ }
+
+ @Test
+ fun testContextWithContext() = runTest {
+ MDC.put("myKey", "myValue")
+ val mainDispatcher = kotlin.coroutines.coroutineContext[ContinuationInterceptor]!!
+ withContext(Dispatchers.Default + MDCContext()) {
+ assertEquals("myValue", MDC.get("myKey"))
+ withContext(mainDispatcher) {
+ assertEquals("myValue", MDC.get("myKey"))
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/js/README.md b/js/README.md
new file mode 100644
index 00000000..4717a705
--- /dev/null
+++ b/js/README.md
@@ -0,0 +1,4 @@
+# Example of Kotlin JS application with coroutines
+
+[example-frontend-js](example-frontend-js/README.md) -- frontend application written in Kotlin/JS
+that uses coroutines to implement animations in imperative style. \ No newline at end of file
diff --git a/js/example-frontend-js/README.md b/js/example-frontend-js/README.md
new file mode 100644
index 00000000..4e534e42
--- /dev/null
+++ b/js/example-frontend-js/README.md
@@ -0,0 +1,18 @@
+# Example JS frontend application with coroutines
+
+Build application with
+
+```
+gradlew :example-frontend-js:bundle
+```
+
+The resulting application can be found in `build/dist` subdirectory.
+
+You can start application with webpack-dev-server using:
+
+```
+gradlew :example-frontend-js:start
+```
+
+Built and deployed application is available at the library documentation site
+[here](https://kotlin.github.io/kotlinx.coroutines/example-frontend-js/index.html).
diff --git a/js/example-frontend-js/build.gradle b/js/example-frontend-js/build.gradle
new file mode 100644
index 00000000..52b8bb97
--- /dev/null
+++ b/js/example-frontend-js/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+apply plugin: 'kotlin-dce-js'
+apply from: rootProject.file('gradle/node-js.gradle')
+
+dependencies {
+ compile "org.jetbrains.kotlinx:kotlinx-html-js:$html_version"
+}
+
+compileKotlin2Js {
+ kotlinOptions {
+ main = "call"
+ }
+}
+
+task bundle(type: NpmTask, dependsOn: [npmInstall, runDceKotlinJs]) {
+ inputs.files(fileTree("$buildDir/kotlin-js-min/main"))
+ inputs.files(fileTree(file("src/main/web")))
+ inputs.file("npm/webpack.config.js")
+ outputs.dir("$buildDir/dist")
+ args = ["run", "bundle"]
+}
+
+task start(type: NpmTask, dependsOn: bundle) {
+ args = ["run", "start"]
+}
+
+// we have not tests but kotlin-dce-js still tries to work with them and crashed.
+// todo: Remove when KT-22028 is fixed
+afterEvaluate {
+ if (tasks.findByName('unpackDependenciesTestKotlinJs')) {
+ tasks.unpackDependenciesTestKotlinJs.enabled = false
+ }
+}
diff --git a/js/example-frontend-js/npm/package.json b/js/example-frontend-js/npm/package.json
new file mode 100644
index 00000000..7668cefb
--- /dev/null
+++ b/js/example-frontend-js/npm/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "example-frontend-js",
+ "version": "$version",
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Kotlin/kotlinx.coroutines.git"
+ },
+ "devDependencies": {
+ "webpack": "4.29.1",
+ "webpack-cli": "3.2.3",
+ "webpack-dev-server": "3.1.14",
+ "html-webpack-plugin": "3.2.0",
+ "uglifyjs-webpack-plugin": "2.1.1",
+ "style-loader": "0.23.1",
+ "css-loader": "2.1.0"
+ },
+ "scripts": {
+ "bundle": "webpack",
+ "start": "webpack-dev-server --open --no-inline"
+ }
+}
diff --git a/js/example-frontend-js/npm/webpack.config.js b/js/example-frontend-js/npm/webpack.config.js
new file mode 100644
index 00000000..2124d875
--- /dev/null
+++ b/js/example-frontend-js/npm/webpack.config.js
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This script is copied to "build" directory and run from there
+
+const webpack = require("webpack");
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
+const path = require("path");
+
+const dist = path.resolve(__dirname, "dist");
+
+module.exports = {
+ mode: "production",
+ entry: {
+ main: "main"
+ },
+ output: {
+ filename: "[name].bundle.js",
+ path: dist,
+ publicPath: ""
+ },
+ devServer: {
+ contentBase: dist
+ },
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: [
+ 'style-loader',
+ 'css-loader'
+ ]
+ }
+ ]
+ },
+ resolve: {
+ modules: [
+ path.resolve(__dirname, "kotlin-js-min/main"),
+ path.resolve(__dirname, "../src/main/web/")
+ ]
+ },
+ devtool: 'source-map',
+ plugins: [
+ new HtmlWebpackPlugin({
+ title: 'Kotlin Coroutines JS Example'
+ }),
+ new UglifyJSPlugin({
+ sourceMap: true
+ })
+ ]
+}; \ No newline at end of file
diff --git a/js/example-frontend-js/src/ExampleMain.kt b/js/example-frontend-js/src/ExampleMain.kt
new file mode 100644
index 00000000..e3952a23
--- /dev/null
+++ b/js/example-frontend-js/src/ExampleMain.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+import kotlinx.coroutines.*
+import kotlinx.html.*
+import kotlinx.html.div
+import kotlinx.html.dom.*
+import kotlinx.html.js.onClickFunction
+import org.w3c.dom.*
+import kotlin.browser.*
+import kotlin.coroutines.*
+import kotlin.math.*
+
+fun main(args: Array<String>) {
+ println("Starting example application...")
+ document.addEventListener("DOMContentLoaded", {
+ Application().start()
+ })
+}
+
+val Double.px get() = "${this}px"
+
+private fun HTMLElement.setSize(w: Double, h: Double) {
+ with(style) {
+ width = w.px
+ height = h.px
+ }
+}
+
+private fun HTMLElement.setPosition(x: Double, y: Double) {
+ with(style) {
+ left = x.px
+ top = y.px
+ }
+}
+
+@Suppress("DEPRECATION")
+private fun random() = kotlin.js.Math.random()
+
+class Application : CoroutineScope {
+ private val body get() = document.body!!
+ private val scene get() = document.getElementById("scene") as HTMLElement
+ private val sw = 800.0
+ private val sh = 600.0
+ private var animationIndex = 0
+ private var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job
+
+ fun start() {
+ body.append.div("content") {
+ h1 {
+ +"Kotlin Coroutines JS Example"
+ }
+ div {
+ button {
+ +"Rect"
+ onClickFunction = { onRect() }
+ }
+ button {
+ +"Circle"
+ onClickFunction = { onCircle() }
+ }
+ button {
+ +"Clear"
+ onClickFunction = { onClear() }
+ }
+ }
+ div {
+ id = "scene"
+ }
+ }
+ scene.setSize(sw, sh)
+ }
+
+ private fun animation(cls: String, size: Double, block: suspend CoroutineScope.(HTMLElement) -> Unit) {
+ val elem = scene.append.div(cls)
+ elem.setSize(size, size)
+ val job = launch {
+ block(elem)
+ }
+
+ job.invokeOnCompletion { scene.removeChild(elem) }
+ }
+
+ private fun onRect() {
+ val index = ++animationIndex
+ val speed = 0.3
+ val rs = 20.0
+ val turnAfter = 5000.0 // seconds
+ var maxX = sw - rs
+ val maxY = sh - rs
+ animation("rect", rs) { rect ->
+ println("Started new 'rect' coroutine #$index")
+ val timer = AnimationTimer()
+ var turnTime = timer.time + turnAfter
+ val turnTimePhase = turnTime - floor(turnTime / turnAfter) * turnAfter
+ var vx = speed
+ var vy = speed
+ var x = 0.0
+ var y = 0.0
+ while (true) {
+ val dt = timer.await()
+ x += vx * dt
+ y += vy * dt
+ if (x > maxX) {
+ x = 2 * maxX - x
+ vx = -vx
+ }
+ if (x < 0) {
+ x = -x
+ vx = -vx
+ }
+ if (y > maxY) {
+ y = 2 * maxY - y
+ vy = -vy
+ }
+ if (y < 0) {
+ y = -y
+ vy = -vy
+ }
+ rect.setPosition(x, y)
+ if (timer.time >= turnTime) {
+ timer.delay(1000) // pause a bit
+ // flip direction
+ val t = vx
+ if (random() > 0.5) {
+ vx = vy
+ vy = -t
+ } else {
+ vx = -vy
+ vy = t
+ }
+ // reset time, but keep turning time phase
+ turnTime = ceil(timer.reset() / turnAfter) * turnAfter + turnTimePhase
+ println("Delayed #$index for a while at ${timer.time}, resumed and turned")
+ }
+ }
+ }
+ }
+
+ private fun onCircle() {
+ val index = ++animationIndex
+ val acceleration = 5e-4
+ val initialRange = 0.7
+ val maxSpeed = 0.4
+ val initialSpeed = 0.1
+ val radius = 20.0
+ animation("circle", radius) { circle ->
+ println("Started new 'circle' coroutine #$index")
+ val timer = AnimationTimer()
+ val initialAngle = random() * 2 * PI
+ var vx = sin(initialAngle) * initialSpeed
+ var vy = cos(initialAngle) * initialSpeed
+ var x = (random() * initialRange + (1 - initialRange) / 2) * sw
+ var y = (random() * initialRange + (1 - initialRange) / 2) * sh
+ while (true) {
+ val dt = timer.await()
+ val dx = sw / 2 - x
+ val dy = sh / 2 - y
+ val dn = sqrt(dx * dx + dy * dy)
+ vx += dx / dn * acceleration * dt
+ vy += dy / dn * acceleration * dt
+ val vn = sqrt(vx * vx + vy * vy)
+ val trim = vn.coerceAtMost(maxSpeed)
+ vx = vx / vn * trim
+ vy = vy / vn * trim
+ x += vx * dt
+ y += vy * dt
+ circle.setPosition(x, y)
+ }
+ }
+
+ }
+
+ private fun onClear() {
+ job.cancel()
+ job = Job()
+ }
+}
+
+class AnimationTimer {
+ var time = window.performance.now()
+
+ suspend fun await(): Double {
+ val newTime = window.awaitAnimationFrame()
+ val dt = newTime - time
+ time = newTime
+ return dt.coerceAtMost(200.0) // at most 200ms
+ }
+
+ fun reset(): Double {
+ time = window.performance.now()
+ return time
+ }
+
+ suspend fun delay(i: Int) {
+ var dt = 0.0
+ while (dt < i) {
+ dt += await()
+ }
+ }
+}
diff --git a/js/example-frontend-js/src/main/web/main.js b/js/example-frontend-js/src/main/web/main.js
new file mode 100644
index 00000000..c0cb691d
--- /dev/null
+++ b/js/example-frontend-js/src/main/web/main.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// ------ Main bundle for example application ------
+
+require("example-frontend");
+require("style.css");
diff --git a/js/example-frontend-js/src/main/web/style.css b/js/example-frontend-js/src/main/web/style.css
new file mode 100644
index 00000000..29dc9ff8
--- /dev/null
+++ b/js/example-frontend-js/src/main/web/style.css
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+#scene {
+ border: #000000 1px solid;
+ position: relative;
+}
+
+.rect {
+ position: absolute;
+ background: red;
+}
+
+.circle {
+ position: absolute;
+ background: #ffa450;
+ border-radius: 50%;
+} \ No newline at end of file
diff --git a/js/js-stub/README.md b/js/js-stub/README.md
new file mode 100644
index 00000000..46d18670
--- /dev/null
+++ b/js/js-stub/README.md
@@ -0,0 +1 @@
+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 b/js/js-stub/build.gradle
new file mode 100644
index 00000000..20067b7c
--- /dev/null
+++ b/js/js-stub/build.gradle
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+compileKotlin {
+ kotlinOptions {
+ freeCompilerArgs += "-Xallow-kotlin-package"
+ }
+} \ No newline at end of file
diff --git a/js/js-stub/src/Performance.kt b/js/js-stub/src/Performance.kt
new file mode 100644
index 00000000..4c63ed00
--- /dev/null
+++ b/js/js-stub/src/Performance.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 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 {
+ abstract fun now(): Double
+} \ No newline at end of file
diff --git a/js/js-stub/src/Promise.kt b/js/js-stub/src/Promise.kt
new file mode 100644
index 00000000..a7d501a6
--- /dev/null
+++ b/js/js-stub/src/Promise.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2016-2018 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> \ No newline at end of file
diff --git a/js/js-stub/src/Window.kt b/js/js-stub/src/Window.kt
new file mode 100644
index 00000000..82c6fcfd
--- /dev/null
+++ b/js/js-stub/src/Window.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2016-2018 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 \ No newline at end of file
diff --git a/knit/README.md b/knit/README.md
new file mode 100644
index 00000000..ca0560fa
--- /dev/null
+++ b/knit/README.md
@@ -0,0 +1,13 @@
+# Knit
+
+This is a very simple tool that produces Kotlin source example files from a markdown document that includes
+snippets of Kotlin code in its body. It is used to produce examples for
+[coroutines guide](../docs/coroutines-guide.md) and other markdown documents.
+It also includes links to the documentation web site into the documents.
+
+## Usage
+
+* In project root directory do:
+ * Run `./gradlew knit`
+* Commit updated documents and examples
+
diff --git a/knit/build.gradle b/knit/build.gradle
new file mode 100644
index 00000000..0410d5d2
--- /dev/null
+++ b/knit/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+apply plugin: "application"
+
+sourceSets {
+ main.kotlin.srcDirs = ['src']
+ main.java.srcDirs = ['src']
+ main.resources.srcDirs = ['resources']
+}
+
+FileTree mdFiles = fileTree(project.rootDir) {
+ include '**/*.md'
+ exclude '**/build/**'
+ exclude '**/.gradle/**'
+ exclude '**/node_modules/**'
+}
+
+mainClassName = "KnitKt"
+
+run.dependsOn rootProject.getTasksByName("dokka", true)
+run.args = mdFiles
+run.workingDir = project.rootDir
+
+task knit(dependsOn: run)
diff --git a/knit/resources/knit.properties b/knit/resources/knit.properties
new file mode 100644
index 00000000..146c1823
--- /dev/null
+++ b/knit/resources/knit.properties
@@ -0,0 +1,9 @@
+#
+# Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
+site.root=https://kotlin.github.io/kotlinx.coroutines
+
+module.roots=. integration reactive ui
+module.marker=build.gradle
+module.docs=build/dokka \ No newline at end of file
diff --git a/knit/src/Knit.kt b/knit/src/Knit.kt
new file mode 100644
index 00000000..30dd678f
--- /dev/null
+++ b/knit/src/Knit.kt
@@ -0,0 +1,598 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import java.io.*
+import java.util.*
+import kotlin.properties.*
+
+// --- props in knit.properties
+
+val knitProperties = ClassLoader.getSystemClassLoader()
+ .getResource("knit.properties").openStream().use { Properties().apply { load(it) } }
+
+val siteRoot = knitProperties.getProperty("site.root")!!
+val moduleRoots = knitProperties.getProperty("module.roots").split(" ")
+val moduleMarker = knitProperties.getProperty("module.marker")!!
+val moduleDocs = knitProperties.getProperty("module.docs")!!
+
+// --- markdown syntax
+
+const val DIRECTIVE_START = "<!--- "
+const val DIRECTIVE_END = "-->"
+
+const val TOC_DIRECTIVE = "TOC"
+const val TOC_REF_DIRECTIVE = "TOC_REF"
+const val KNIT_DIRECTIVE = "KNIT"
+const val INCLUDE_DIRECTIVE = "INCLUDE"
+const val CLEAR_DIRECTIVE = "CLEAR"
+const val TEST_DIRECTIVE = "TEST"
+
+const val KNIT_AUTONUMBER_PLACEHOLDER = '#'
+const val KNIT_AUTONUMBER_REGEX = "([0-9a-z]+)"
+
+const val TEST_OUT_DIRECTIVE = "TEST_OUT"
+
+const val MODULE_DIRECTIVE = "MODULE"
+const val INDEX_DIRECTIVE = "INDEX"
+
+const val CODE_START = "```kotlin"
+const val CODE_END = "```"
+
+const val SAMPLE_START = "//sampleStart"
+const val SAMPLE_END = "//sampleEnd"
+
+const val TEST_START = "```text"
+const val TEST_END = "```"
+
+const val SECTION_START = "##"
+
+const val PACKAGE_PREFIX = "package "
+const val STARTS_WITH_PREDICATE = "STARTS_WITH"
+const val ARBITRARY_TIME_PREDICATE = "ARBITRARY_TIME"
+const val FLEXIBLE_TIME_PREDICATE = "FLEXIBLE_TIME"
+const val FLEXIBLE_THREAD_PREDICATE = "FLEXIBLE_THREAD"
+const val LINES_START_UNORDERED_PREDICATE = "LINES_START_UNORDERED"
+const val EXCEPTION_MODE = "EXCEPTION"
+const val LINES_START_PREDICATE = "LINES_START"
+
+val API_REF_REGEX = Regex("(^|[ \\](])\\[([A-Za-z0-9_().]+)]($|[^\\[(])")
+val LINK_DEF_REGEX = Regex("^\\[([A-Za-z0-9_().]+)]: .*")
+
+val tocRefMap = HashMap<File, List<TocRef>>()
+val fileSet = HashSet<File>()
+val fileQueue = ArrayDeque<File>()
+
+fun main(args: Array<String>) {
+ if (args.isEmpty()) {
+ println("Usage: Knit <markdown-files>")
+ return
+ }
+ args.map { File(it) }.toCollection(fileQueue)
+ fileQueue.toCollection(fileSet)
+ while (!fileQueue.isEmpty()) {
+ if (!knit(fileQueue.removeFirst())) System.exit(1) // abort on first error with error exit code
+ }
+}
+
+fun knit(markdownFile: File): Boolean {
+ println("*** Reading $markdownFile")
+ val tocLines = arrayListOf<String>()
+ var knitRegex: Regex? = null
+ var knitAutonumberGroup = 0
+ var knitAutonumberDigits = 0
+ var knitAutonumberIndex = 1
+ val includes = arrayListOf<Include>()
+ val codeLines = arrayListOf<String>()
+ val testLines = arrayListOf<String>()
+ var testOut: String? = null
+ val testOutLines = arrayListOf<String>()
+ var lastPgk: String? = null
+ val files = mutableSetOf<File>()
+ val allApiRefs = arrayListOf<ApiRef>()
+ val remainingApiRefNames = mutableSetOf<String>()
+ var moduleName: String by Delegates.notNull()
+ var docsRoot: String by Delegates.notNull()
+ var retryKnitLater = false
+ val tocRefs = ArrayList<TocRef>().also { tocRefMap[markdownFile] = it }
+ // read markdown file
+ val markdown = markdownFile.withMarkdownTextReader {
+ mainLoop@ while (true) {
+ val inLine = readLine() ?: break
+ val directive = directive(inLine)
+ if (directive != null && markdownPart == MarkdownPart.TOC) {
+ markdownPart = MarkdownPart.POST_TOC
+ postTocText += inLine
+ }
+ when (directive?.name) {
+ TOC_DIRECTIVE -> {
+ requireSingleLine(directive)
+ require(directive.param.isEmpty()) { "$TOC_DIRECTIVE directive must not have parameters" }
+ require(markdownPart == MarkdownPart.PRE_TOC) { "Only one TOC directive is supported" }
+ markdownPart = MarkdownPart.TOC
+ }
+ TOC_REF_DIRECTIVE -> {
+ requireSingleLine(directive)
+ require(!directive.param.isEmpty()) { "$TOC_REF_DIRECTIVE directive must include reference file path" }
+ val refPath = directive.param
+ val refFile = File(markdownFile.parent, refPath.replace('/', File.separatorChar))
+ require(fileSet.contains(refFile)) { "Referenced file $refFile is missing from the processed file set" }
+ val toc = tocRefMap[refFile]
+ if (toc == null) {
+ retryKnitLater = true // put this file at the end of the queue and retry later
+ } else {
+ val lines = toc.map { (levelPrefix, name, ref) ->
+ "$levelPrefix <a name='$ref'></a>[$name]($refPath#$ref)"
+ }
+ if (!replaceUntilNextDirective(lines)) error("Unexpected end of file after $TOC_REF_DIRECTIVE")
+ }
+ }
+ KNIT_DIRECTIVE -> {
+ requireSingleLine(directive)
+ require(!directive.param.isEmpty()) { "$KNIT_DIRECTIVE directive must include regex parameter" }
+ require(knitRegex == null) { "Only one KNIT directive is supported"}
+ var str = directive.param
+ val i = str.indexOf(KNIT_AUTONUMBER_PLACEHOLDER)
+ if (i >= 0) {
+ val j = str.lastIndexOf(KNIT_AUTONUMBER_PLACEHOLDER)
+ knitAutonumberDigits = j - i + 1
+ require(str.substring(i, j + 1) == KNIT_AUTONUMBER_PLACEHOLDER.toString().repeat(knitAutonumberDigits)) {
+ "$KNIT_DIRECTIVE can only use a contiguous range of '$KNIT_AUTONUMBER_PLACEHOLDER' for auto-numbering"
+ }
+ knitAutonumberGroup = str.substring(0, i).count { it == '(' } + 2 // note: it does not understand escaped open braces
+ str = str.substring(0, i) + KNIT_AUTONUMBER_REGEX + str.substring(j + 1)
+ }
+ knitRegex = Regex("\\((" + str + ")\\)")
+ continue@mainLoop
+ }
+ INCLUDE_DIRECTIVE -> {
+ if (directive.param.isEmpty()) {
+ require(!directive.singleLine) { "$INCLUDE_DIRECTIVE directive without parameters must not be single line" }
+ readUntilTo(DIRECTIVE_END, codeLines)
+ } else {
+ val include = Include(Regex(directive.param))
+ if (directive.singleLine) {
+ include.lines += codeLines
+ codeLines.clear()
+ } else {
+ readUntilTo(DIRECTIVE_END, include.lines)
+ }
+ includes += include
+ }
+ continue@mainLoop
+ }
+ CLEAR_DIRECTIVE -> {
+ requireSingleLine(directive)
+ require(directive.param.isEmpty()) { "$CLEAR_DIRECTIVE directive must not have parameters" }
+ codeLines.clear()
+ continue@mainLoop
+ }
+ TEST_OUT_DIRECTIVE -> {
+ require(!directive.param.isEmpty()) { "$TEST_OUT_DIRECTIVE directive must include file name parameter" }
+ flushTestOut(markdownFile.parentFile, testOut, testOutLines)
+ testOut = directive.param
+ readUntil(DIRECTIVE_END).forEach { testOutLines += it }
+ }
+ TEST_DIRECTIVE -> {
+ require(lastPgk != null) { "'$PACKAGE_PREFIX' prefix was not found in emitted code"}
+ require(testOut != null) { "$TEST_OUT_DIRECTIVE directive was not specified" }
+ val predicate = directive.param
+ if (testLines.isEmpty()) {
+ if (directive.singleLine) {
+ require(!predicate.isEmpty()) { "$TEST_OUT_DIRECTIVE must be preceded by $TEST_START block or contain test predicate"}
+ } else
+ testLines += readUntil(DIRECTIVE_END)
+ } else {
+ requireSingleLine(directive)
+ }
+ makeTest(testOutLines, lastPgk!!, testLines, predicate)
+ testLines.clear()
+ }
+ MODULE_DIRECTIVE -> {
+ requireSingleLine(directive)
+ moduleName = directive.param
+ docsRoot = findModuleRootDir(moduleName) + "/" + moduleDocs + "/" + moduleName
+ }
+ INDEX_DIRECTIVE -> {
+ requireSingleLine(directive)
+ val indexLines = processApiIndex("$siteRoot/$moduleName", docsRoot, directive.param, remainingApiRefNames)
+ ?: throw IllegalArgumentException("Failed to load index for ${directive.param}")
+ if (!replaceUntilNextDirective(indexLines)) error("Unexpected end of file after $INDEX_DIRECTIVE")
+ }
+ }
+ if (inLine.startsWith(CODE_START)) {
+ require(testOut == null || testLines.isEmpty()) { "Previous test was not emitted with $TEST_DIRECTIVE" }
+ codeLines += ""
+ readUntilTo(CODE_END, codeLines) { line ->
+ !line.startsWith(SAMPLE_START) && !line.startsWith(SAMPLE_END)
+ }
+ continue@mainLoop
+ }
+ if (inLine.startsWith(TEST_START)) {
+ require(testOut == null || testLines.isEmpty()) { "Previous test was not emitted with $TEST_DIRECTIVE" }
+ readUntilTo(TEST_END, testLines)
+ continue@mainLoop
+ }
+ if (inLine.startsWith(SECTION_START) && markdownPart == MarkdownPart.POST_TOC) {
+ val i = inLine.indexOf(' ')
+ require(i >= 2) { "Invalid section start" }
+ val name = inLine.substring(i + 1).trim()
+ val levelPrefix = " ".repeat(i - 2) + "*"
+ val sectionRef = makeSectionRef(name)
+ tocLines += "$levelPrefix [$name](#$sectionRef)"
+ tocRefs += TocRef(levelPrefix, name, sectionRef)
+ continue@mainLoop
+ }
+ val linkDefMatch = LINK_DEF_REGEX.matchEntire(inLine)
+ if (linkDefMatch != null) {
+ val name = linkDefMatch.groups[1]!!.value
+ remainingApiRefNames -= name
+ } else {
+ for (match in API_REF_REGEX.findAll(inLine)) {
+ val apiRef = ApiRef(lineNumber, match.groups[2]!!.value)
+ allApiRefs += apiRef
+ remainingApiRefNames += apiRef.name
+ }
+ }
+ knitRegex?.find(inLine)?.let knitRegexMatch@{ knitMatch ->
+ val fileName = knitMatch.groups[1]!!.value
+ if (knitAutonumberDigits != 0) {
+ val numGroup = knitMatch.groups[knitAutonumberGroup]!!
+ val num = knitAutonumberIndex.toString().padStart(knitAutonumberDigits, '0')
+ if (numGroup.value != num) { // update and retry with this line if a different number
+ val r = numGroup.range
+ val newLine = inLine.substring(0, r.first) + num + inLine.substring(r.last + 1)
+ updateLineAndRetry(newLine)
+ return@knitRegexMatch
+ }
+ }
+ knitAutonumberIndex++
+ val file = File(markdownFile.parentFile, fileName)
+ require(files.add(file)) { "Duplicate file: $file"}
+ println("Knitting $file ...")
+ val outLines = arrayListOf<String>()
+ for (include in includes) {
+ val includeMatch = include.regex.matchEntire(fileName) ?: continue
+ include.lines.forEach { includeLine ->
+ val line = makeReplacements(includeLine, includeMatch)
+ if (line.startsWith(PACKAGE_PREFIX))
+ lastPgk = line.substring(PACKAGE_PREFIX.length).trim()
+ outLines += line
+ }
+ }
+ for (code in codeLines) {
+ outLines += code.replace("System.currentTimeMillis()", "currentTimeMillis()")
+ }
+ codeLines.clear()
+ writeLinesIfNeeded(file, outLines)
+ }
+ }
+ } ?: return false // false when failed
+ // bailout if retry was requested
+ if (retryKnitLater) {
+ fileQueue.add(markdownFile)
+ return true
+ }
+ // update markdown file with toc
+ val newLines = buildList<String> {
+ addAll(markdown.preTocText)
+ if (!tocLines.isEmpty()) {
+ add("")
+ addAll(tocLines)
+ add("")
+ }
+ addAll(markdown.postTocText)
+ }
+ if (newLines != markdown.inText) writeLines(markdownFile, newLines)
+ // check apiRefs
+ for (apiRef in allApiRefs) {
+ if (apiRef.name in remainingApiRefNames) {
+ println("WARNING: $markdownFile: ${apiRef.line}: Broken reference to [${apiRef.name}]")
+ }
+ }
+ // write test output
+ flushTestOut(markdownFile.parentFile, testOut, testOutLines)
+ return true
+}
+
+data class TocRef(val levelPrefix: String, val name: String, val ref: String)
+
+fun makeTest(testOutLines: MutableList<String>, pgk: String, test: List<String>, predicate: String) {
+ val funName = buildString {
+ var cap = true
+ for (c in pgk) {
+ if (c == '.') {
+ cap = true
+ } else {
+ append(if (cap) c.toUpperCase() else c)
+ cap = false
+ }
+ }
+ }
+ testOutLines += ""
+ testOutLines += " @Test"
+ testOutLines += " fun test$funName() {"
+ val prefix = " test(\"$funName\") { $pgk.main() }"
+ when (predicate) {
+ "" -> makeTestLines(testOutLines, prefix, "verifyLines", test)
+ STARTS_WITH_PREDICATE -> makeTestLines(testOutLines, prefix, "verifyLinesStartWith", test)
+ ARBITRARY_TIME_PREDICATE -> makeTestLines(testOutLines, prefix, "verifyLinesArbitraryTime", test)
+ FLEXIBLE_TIME_PREDICATE -> makeTestLines(testOutLines, prefix, "verifyLinesFlexibleTime", test)
+ FLEXIBLE_THREAD_PREDICATE -> makeTestLines(testOutLines, prefix, "verifyLinesFlexibleThread", test)
+ LINES_START_UNORDERED_PREDICATE -> makeTestLines(testOutLines, prefix, "verifyLinesStartUnordered", test)
+ EXCEPTION_MODE -> makeTestLines(testOutLines, prefix, "verifyExceptions", test)
+ LINES_START_PREDICATE -> makeTestLines(testOutLines, prefix, "verifyLinesStart", test)
+ else -> {
+ testOutLines += "$prefix.also { lines ->"
+ testOutLines += " check($predicate)"
+ testOutLines += " }"
+ }
+ }
+ testOutLines += " }"
+}
+
+private fun makeTestLines(testOutLines: MutableList<String>, prefix: String, method: String, test: List<String>) {
+ testOutLines += "$prefix.$method("
+ for ((index, testLine) in test.withIndex()) {
+ val commaOpt = if (index < test.size - 1) "," else ""
+ val escapedLine = testLine.replace("\"", "\\\"")
+ testOutLines += " \"$escapedLine\"$commaOpt"
+ }
+ testOutLines += " )"
+}
+
+private fun makeReplacements(line: String, match: MatchResult): String {
+ var result = line
+ for ((id, group) in match.groups.withIndex()) {
+ if (group != null)
+ result = result.replace("\$\$$id", group.value)
+ }
+ return result
+}
+
+private fun flushTestOut(parentDir: File?, testOut: String?, testOutLines: MutableList<String>) {
+ if (testOut == null) return
+ val file = File(parentDir, testOut)
+ testOutLines += "}"
+ writeLinesIfNeeded(file, testOutLines)
+ testOutLines.clear()
+}
+
+private fun MarkdownTextReader.readUntil(marker: String): List<String> =
+ arrayListOf<String>().also { readUntilTo(marker, it) }
+
+private fun MarkdownTextReader.readUntilTo(marker: String, list: MutableList<String>, linePredicate: (String) -> Boolean = { true }) {
+ while (true) {
+ val line = readLine() ?: break
+ if (line.startsWith(marker)) break
+ if (linePredicate(line)) list += line
+ }
+}
+
+private inline fun <T> buildList(block: ArrayList<T>.() -> Unit): List<T> {
+ val result = arrayListOf<T>()
+ result.block()
+ return result
+}
+
+private fun requireSingleLine(directive: Directive) {
+ require(directive.singleLine) { "${directive.name} directive must end on the same line with '$DIRECTIVE_END'" }
+}
+
+fun makeSectionRef(name: String): String = name
+ .replace(' ', '-')
+ .replace(".", "")
+ .replace(",", "")
+ .replace("(", "")
+ .replace(")", "")
+ .replace("`", "")
+ .toLowerCase()
+
+class Include(val regex: Regex, val lines: MutableList<String> = arrayListOf())
+
+class Directive(
+ val name: String,
+ val param: String,
+ val singleLine: Boolean
+)
+
+fun directive(line: String): Directive? {
+ if (!line.startsWith(DIRECTIVE_START)) return null
+ var s = line.substring(DIRECTIVE_START.length).trim()
+ val singleLine = s.endsWith(DIRECTIVE_END)
+ if (singleLine) s = s.substring(0, s.length - DIRECTIVE_END.length)
+ val i = s.indexOf(' ')
+ val name = if (i < 0) s else s.substring(0, i)
+ val param = if (i < 0) "" else s.substring(i).trim()
+ return Directive(name, param, singleLine)
+}
+
+class ApiRef(val line: Int, val name: String)
+
+enum class MarkdownPart { PRE_TOC, TOC, POST_TOC }
+
+class MarkdownTextReader(r: Reader) : LineNumberReader(r) {
+ val inText = arrayListOf<String>()
+ val preTocText = arrayListOf<String>()
+ val postTocText = arrayListOf<String>()
+ var markdownPart: MarkdownPart = MarkdownPart.PRE_TOC
+ var skip = false
+ var putBackLine: String? = null
+
+ val outText: MutableList<String> get() = when (markdownPart) {
+ MarkdownPart.PRE_TOC -> preTocText
+ MarkdownPart.POST_TOC -> postTocText
+ else -> throw IllegalStateException("Wrong state: $markdownPart")
+ }
+
+ override fun readLine(): String? {
+ putBackLine?.let {
+ putBackLine = null
+ return it
+ }
+ val line = super.readLine() ?: return null
+ inText += line
+ if (!skip && markdownPart != MarkdownPart.TOC)
+ outText += line
+ return line
+ }
+
+ fun updateLineAndRetry(line: String) {
+ outText.removeAt(outText.lastIndex)
+ outText += line
+ putBackLine = line
+ }
+
+ fun replaceUntilNextDirective(lines: List<String>): Boolean {
+ skip = true
+ while (true) {
+ val skipLine = readLine() ?: return false
+ if (directive(skipLine) != null) {
+ putBackLine = skipLine
+ break
+ }
+ }
+ skip = false
+ outText += lines
+ outText += putBackLine!!
+ return true
+ }
+}
+
+fun <T : LineNumberReader> File.withLineNumberReader(factory: (Reader) -> T, block: T.() -> Unit): T? {
+ val reader = factory(reader())
+ reader.use {
+ try {
+ it.block()
+ } catch (e: Exception) {
+ println("ERROR: ${this@withLineNumberReader}: ${it.lineNumber}: ${e.message}")
+ return null
+ }
+ }
+ return reader
+}
+
+fun File.withMarkdownTextReader(block: MarkdownTextReader.() -> Unit): MarkdownTextReader? =
+ withLineNumberReader<MarkdownTextReader>(::MarkdownTextReader, block)
+
+fun writeLinesIfNeeded(file: File, outLines: List<String>) {
+ val oldLines = try {
+ file.readLines()
+ } catch (e: IOException) {
+ emptyList<String>()
+ }
+ if (outLines != oldLines) writeLines(file, outLines)
+}
+
+fun writeLines(file: File, lines: List<String>) {
+ println(" Writing $file ...")
+ file.parentFile?.mkdirs()
+ file.printWriter().use { out ->
+ lines.forEach { out.println(it) }
+ }
+}
+
+fun findModuleRootDir(name: String): String =
+ moduleRoots
+ .map { "$it/$name" }
+ .firstOrNull { File("$it/$moduleMarker").exists() }
+ ?: throw IllegalArgumentException("Module $name is not found in any of the module root dirs")
+
+data class ApiIndexKey(
+ val docsRoot: String,
+ val pkg: String
+)
+
+val apiIndexCache: MutableMap<ApiIndexKey, Map<String, List<String>>> = HashMap()
+
+val REF_LINE_REGEX = Regex("<a href=\"([a-z0-9_/.\\-]+)\">([a-zA-z0-9.]+)</a>")
+val INDEX_HTML = "/index.html"
+val INDEX_MD = "/index.md"
+val FUNCTIONS_SECTION_HEADER = "### Functions"
+
+fun HashMap<String, MutableList<String>>.putUnambiguous(key: String, value: String) {
+ val oldValue = this[key]
+ if (oldValue != null) {
+ oldValue.add(value)
+ put(key, oldValue)
+ } else {
+ put(key, mutableListOf(value))
+ }
+}
+
+fun loadApiIndex(
+ docsRoot: String,
+ path: String,
+ pkg: String,
+ namePrefix: String = ""
+): Map<String, MutableList<String>>? {
+ val fileName = "$docsRoot/$path$INDEX_MD"
+ val visited = mutableSetOf<String>()
+ val map = HashMap<String, MutableList<String>>()
+ var inFunctionsSection = false
+ File(fileName).withLineNumberReader(::LineNumberReader) {
+ while (true) {
+ val line = readLine() ?: break
+ if (line == FUNCTIONS_SECTION_HEADER) inFunctionsSection = true
+ val result = REF_LINE_REGEX.matchEntire(line) ?: continue
+ val link = result.groups[1]!!.value
+ if (link.startsWith("..")) continue // ignore cross-references
+ val absLink = "$path/$link"
+ var name = result.groups[2]!!.value
+ // a special disambiguation fix for pseudo-constructor functions
+ if (inFunctionsSection && name[0] in 'A'..'Z') name += "()"
+ val refName = namePrefix + name
+ val fqName = "$pkg.$refName"
+ // Put shorter names for extensions on 3rd party classes (prefix is FQname of those classes)
+ if (namePrefix != "" && namePrefix[0] in 'a'..'z') {
+ val i = namePrefix.dropLast(1).lastIndexOf('.')
+ if (i >= 0) map.putUnambiguous(namePrefix.substring(i + 1) + name, absLink)
+ map.putUnambiguous(name, absLink)
+ }
+ // Disambiguate lower-case names with leading underscore (e.g. Flow class vs flow builder ambiguity)
+ if (namePrefix == "" && name[0] in 'a'..'z') {
+ map.putUnambiguous("_$name", absLink)
+ }
+ // Always put fully qualified names
+ map.putUnambiguous(refName, absLink)
+ map.putUnambiguous(fqName, absLink)
+ if (link.endsWith(INDEX_HTML)) {
+ if (visited.add(link)) {
+ val path2 = path + "/" + link.substring(0, link.length - INDEX_HTML.length)
+ map += loadApiIndex(docsRoot, path2, pkg, "$refName.")
+ ?: throw IllegalArgumentException("Failed to parse $docsRoot/$path2")
+ }
+ }
+ }
+ } ?: return null // return null on failure
+ return map
+}
+
+fun processApiIndex(
+ siteRoot: String,
+ docsRoot: String,
+ pkg: String,
+ remainingApiRefNames: MutableSet<String>
+): List<String>? {
+ val key = ApiIndexKey(docsRoot, pkg)
+ val map = apiIndexCache.getOrPut(key, {
+ print("Parsing API docs at $docsRoot/$pkg: ")
+ val result = loadApiIndex(docsRoot, pkg, pkg) ?: return null // null on failure
+ println("${result.size} definitions")
+ result
+ })
+ val indexList = arrayListOf<String>()
+ val it = remainingApiRefNames.iterator()
+ while (it.hasNext()) {
+ val refName = it.next()
+ val refLink = map[refName] ?: continue
+ if (refLink.size > 1) {
+ println("INFO: Ambiguous reference to [$refName]: $refLink, taking the shortest one")
+ }
+
+ val link = refLink.minBy { it.length }
+ indexList += "[$refName]: $siteRoot/$link"
+ it.remove()
+ }
+ return indexList
+}
diff --git a/kotlinx-coroutines-bom/build.gradle b/kotlinx-coroutines-bom/build.gradle
new file mode 100644
index 00000000..c6675dd3
--- /dev/null
+++ b/kotlinx-coroutines-bom/build.gradle
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+plugins {
+ id 'io.spring.dependency-management'
+}
+
+def name = project.name
+
+dependencyManagement {
+ dependencies {
+ rootProject.subprojects.each {
+ if (ext.unpublished.contains(it.name)) return
+ if (it.name == name) return
+ if (!it.plugins.hasPlugin('maven-publish')) return
+ evaluationDependsOn(it.path)
+ it.publishing.publications.all {
+ if (it.artifactId.endsWith("-kotlinMultiplatform")) return
+ if (it.artifactId.endsWith("-metadata")) return
+ dependency(group: it.groupId, name: it.artifactId, version: it.version)
+ }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/README.md b/kotlinx-coroutines-core/README.md
new file mode 100644
index 00000000..5fe32981
--- /dev/null
+++ b/kotlinx-coroutines-core/README.md
@@ -0,0 +1,140 @@
+# Module kotlinx-coroutines-core
+
+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
+
+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
+
+More context elements:
+
+| **Name** | **Description**
+| --------------------------- | ---------------
+| [NonCancellable] | A non-cancelable job that is always active
+| [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
+| [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
+
+Cancellation support for user-defined suspending functions is available with [suspendCancellableCoroutine]
+helper function. [NonCancellable] job object is provided to suppress cancellation with
+`withContext(NonCancellable) {...}` block of code.
+
+[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
+
+# Package kotlinx.coroutines
+
+General-purpose coroutine builders, contexts, and helper functions.
+
+# Package kotlinx.coroutines.sync
+
+Synchronization primitives (mutex).
+
+# Package kotlinx.coroutines.channels
+
+Channels &mdash; non-blocking primitives for communicating a stream of elements between coroutines.
+
+# Package kotlinx.coroutines.flow
+
+Flow &mdash; asynchronous cold stream of elements.
+
+# Package kotlinx.coroutines.selects
+
+Select expression to perform multiple suspending operations simultaneously until one of them succeeds.
+
+# Package kotlinx.coroutines.intrinsics
+
+Low-level primitives for finer-grained control of coroutines.
+
+# Package kotlinx.coroutines.test
+
+Obsolete and deprecated module to test coroutines. Replaced with `kotlinx-coroutines-test` module.
+
+<!--- 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
+[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
+[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
+<!--- 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
+[kotlinx.coroutines.channels.Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+[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]: 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.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
+<!--- 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
+<!--- INDEX kotlinx.coroutines.test -->
+<!--- END -->
diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle
new file mode 100644
index 00000000..c329497f
--- /dev/null
+++ b/kotlinx-coroutines-core/build.gradle
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+apply plugin: 'kotlin-multiplatform'
+apply from: rootProject.file("gradle/targets.gradle")
+apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle")
+apply from: rootProject.file("gradle/compile-common.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')
+
+/*
+ * All platform plugins and configuration magic happens here instead of build.gradle
+ * because JMV-only projects depend on core, thus core should always be initialized before configuration.
+ */
+kotlin {
+ configure(sourceSets) {
+ def srcDir = name.endsWith('Main') ? 'src' : 'test'
+ def platform = name[0..-5]
+ kotlin.srcDir "$platform/$srcDir"
+ if (name == "jvmMain") {
+ resources.srcDirs = ["$platform/resources"]
+ } else if (name == "jvmTest") {
+ resources.srcDirs = ["$platform/test-resources"]
+ }
+ languageSettings {
+ progressiveMode = true
+ experimentalAnnotations.each { useExperimentalAnnotation(it) }
+ }
+ }
+
+ configure(targets) {
+ def targetName = it.name
+ compilations.all { compilation ->
+ def compileTask = tasks.getByName(compilation.compileKotlinTaskName)
+ // binary compatibility support
+ if (targetName.contains("jvm") && compilation.compilationName == "main") {
+ compileTask.kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"]
+ }
+ }
+ }
+}
+
+configurations {
+ configureKotlinJvmPlatform(kotlinCompilerPluginClasspath)
+}
+
+kotlin.sourceSets {
+ jvmTest.dependencies {
+ api "com.devexperts.lincheck:lincheck:$lincheck_version"
+ api "com.esotericsoftware:kryo:4.0.0"
+ implementation project (":android-unit-tests")
+ }
+}
+
+task checkJdk16() {
+ // only fail w/o JDK_16 when actually trying to compile, not during project setup phase
+ doLast {
+ if (!System.env.JDK_16) {
+ throw new GradleException("JDK_16 environment variable is not defined. " +
+ "Can't build against JDK 1.6 runtime and run JDK 1.6 compatibility tests. " +
+ "Please ensure JDK 1.6 is installed and that JDK_16 points to it.")
+ }
+ }
+}
+
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
+ kotlinOptions.jdkHome = System.env.JDK_16
+ // only fail when actually trying to compile, not during project setup phase
+ dependsOn(checkJdk16)
+}
+
+jvmTest {
+ minHeapSize = '1g'
+ maxHeapSize = '1g'
+ enableAssertions = true
+ systemProperty 'java.security.manager', 'kotlinx.coroutines.TestSecurityManager'
+ exclude '**/*LFStressTest.*'
+ systemProperty 'kotlinx.coroutines.scheduler.keep.alive.sec', '100000' // any unpark problem hangs test
+}
+
+task lockFreedomTest(type: Test, dependsOn: compileTestKotlinJvm) {
+ classpath = files { jvmTest.classpath }
+ testClassesDirs = files { jvmTest.testClassesDirs }
+ include '**/*LFStressTest.*'
+ enableAssertions = true
+ testLogging.showStandardStreams = true
+}
+
+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.*' // lic-check tests use LinChecker which needs JDK8
+ exclude '**/exceptions/**' // exceptions tests check suppressed exception which needs JDK8
+ exclude '**/ExceptionsGuideTest.*'
+}
+
+// Run these tests only during nightly stress test
+jdk16Test.onlyIf { project.properties['stressTest'] != null }
+
+// Always run those tests
+task moreTest(dependsOn: [lockFreedomTest, jdk16Test])
+build.dependsOn moreTest
+
+task testsJar(type: Jar, dependsOn: jvmTestClasses) {
+ classifier = 'tests'
+ from compileTestKotlinJvm.destinationDir
+}
+
+artifacts {
+ archives testsJar
+}
diff --git a/kotlinx-coroutines-core/common/README.md b/kotlinx-coroutines-core/common/README.md
new file mode 100644
index 00000000..e59392ee
--- /dev/null
+++ b/kotlinx-coroutines-core/common/README.md
@@ -0,0 +1,154 @@
+# Module kotlinx-coroutines-core
+
+Core primitives to work with coroutines available on all platforms.
+
+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
+| [actor][kotlinx.coroutines.channels.actor] | [SendChannel][kotlinx.coroutines.channels.SendChannel] | [ActorScope][kotlinx.coroutines.channels.ActorScope] | Processes a stream of messages
+
+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
+| [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
+
+More context elements:
+
+| **Name** | **Description**
+| --------------------------- | ---------------
+| [NonCancellable] | A non-cancelable job that is always active
+| [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
+| [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
+
+Cancellation support for user-defined suspending functions is available with [suspendCancellableCoroutine]
+helper function. [NonCancellable] job object is provided to suppress cancellation with
+`withContext(NonCancellable) {...}` block of code.
+
+[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
+
+This module provides debugging facilities for coroutines (run JVM with `-ea` or `-Dkotlinx.coroutines.debug` options)
+and [newCoroutineContext] function to write user-defined coroutine builders that work with these
+debugging facilities. See [DEBUG_PROPERTY_NAME] for more details.
+
+This module provides a special CoroutineContext type [TestCoroutineCoroutineContext][kotlinx.coroutines.test.TestCoroutineContext] that
+allows the writer of code that contains Coroutines with delays and timeouts to write non-flaky unit-tests for that code allowing these tests to
+terminate in near zero time. See the documentation for this class for more information.
+
+# Package kotlinx.coroutines
+
+General-purpose coroutine builders, contexts, and helper functions.
+
+# Package kotlinx.coroutines.flow
+
+Flow -- primitive to work with asynchronous and event-based streams of data.
+
+# Package kotlinx.coroutines.sync
+
+Synchronization primitives (mutex).
+
+# Package kotlinx.coroutines.channels
+
+Channels -- non-blocking primitives for communicating a stream of elements between coroutines.
+
+# Package kotlinx.coroutines.selects
+
+Select expression to perform multiple suspending operations simultaneously until one of them succeeds.
+
+# Package kotlinx.coroutines.intrinsics
+
+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
+[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
+[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
+[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
+[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
+[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
+[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
+[kotlinx.coroutines.channels.actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
+[kotlinx.coroutines.channels.SendChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html
+[kotlinx.coroutines.channels.ActorScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-actor-scope/index.html
+[kotlinx.coroutines.channels.Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+[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.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
+<!--- 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
+<!--- 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
new file mode 100644
index 00000000..014a7425
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("DEPRECATION_ERROR")
+
+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.
+ *
+ * This class implements completion [Continuation], [Job], and [CoroutineScope] interfaces.
+ * It stores the result of continuation in the state of the job.
+ * This coroutine waits for children coroutines to finish before completing and
+ * fails through an intermediate _failing_ state.
+ *
+ * The following methods are available for override:
+ *
+ * * [onStart] is invoked when the coroutine was created in non-active state and is being [started][Job.start].
+ * * [onCancelling] is invoked as soon as the coroutine starts being cancelled for any reason (or completes).
+ * * [onCompleted] is invoked when the coroutine completes with a value.
+ * * [onCancelled] in invoked when the coroutine completes with an exception (cancelled).
+ *
+ * @param parentContext the context of the parent coroutine.
+ * @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.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public abstract class AbstractCoroutine<in T>(
+ /**
+ * The context of the parent coroutine.
+ */
+ @JvmField
+ protected val parentContext: CoroutineContext,
+ active: Boolean = true
+) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
+ /**
+ * The context of this coroutine that includes this coroutine as a [Job].
+ */
+ @Suppress("LeakingThis")
+ public final override val context: CoroutineContext = parentContext + this
+
+ /**
+ * The context of this scope which is the same as the [context] of this coroutine.
+ */
+ public override val coroutineContext: CoroutineContext get() = context
+
+ 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.
+ */
+ protected open fun onCompleted(value: T) {}
+
+ /**
+ * This function is invoked once when the job was cancelled with the specified [cause],
+ * right before all the waiters for coroutine's completion are notified.
+ *
+ * **Note:** the state of the coroutine might not be final yet in this function and should not be queried.
+ * You can use [completionCause] and [completionCauseHandled] to recover parameters that we passed
+ * to this `onCancelled` invocation only when [isCompleted] returns `true`.
+ *
+ * @param cause The cancellation (failure) cause
+ * @param handled `true` if the exception was handled by parent (always `true` when it is a [CancellationException])
+ */
+ protected open fun onCancelled(cause: Throwable, handled: Boolean) {}
+
+ @Suppress("UNCHECKED_CAST")
+ protected final override fun onCompletionInternal(state: Any?) {
+ if (state is CompletedExceptionally)
+ onCancelled(state.cause, state.handled)
+ else
+ onCompleted(state as T)
+ }
+
+ internal open val defaultResumeMode: Int get() = MODE_ATOMIC_DEFAULT
+
+ /**
+ * Completes execution of this with coroutine with the specified result.
+ */
+ public final override fun resumeWith(result: Result<T>) {
+ makeCompletingOnce(result.toState(), defaultResumeMode)
+ }
+
+ internal final override fun handleOnCompletionException(exception: Throwable) {
+ handleCoroutineException(context, exception)
+ }
+
+ internal override fun nameString(): String {
+ val coroutineName = context.coroutineName ?: return super.nameString()
+ return "\"$coroutineName\":${super.nameString()}"
+ }
+
+ /**
+ * 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].
+ * * [UNDISPATCHED] uses [startCoroutineUndispatched].
+ * * [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
new file mode 100644
index 00000000..742a7d7c
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Annotations.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * 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
+ * the semantics of their behavior may change in some way that may break some code.
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@Experimental(level = Experimental.Level.WARNING)
+public annotation class ExperimentalCoroutinesApi
+
+/**
+ * Marks [Flow]-related API as a feature preview.
+ *
+ * Flow preview has **no** backward compatibility guarantees, including both binary and source compatibility.
+ * Its API and semantics can and will be changed in next releases.
+ *
+ * Feature preview can be used to evaluate its real-world strengths and weaknesses, gather and provide feedback.
+ * According to the feedback, [Flow] will be refined on its road to stabilization and promotion to a stable API.
+ *
+ * The best way to speed up preview feature promotion is providing the feedback on the feature.
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@Experimental(level = Experimental.Level.WARNING)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY)
+public annotation class FlowPreview
+
+/**
+ * Marks declarations that are **obsolete** in coroutines API, which means that the design of the corresponding
+ * declarations has serious known flaws and they will be redesigned in the future.
+ * Roughly speaking, these declarations will be deprecated in the future but there is no replacement for them yet,
+ * so they cannot be deprecated right away.
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@Experimental(level = Experimental.Level.WARNING)
+public annotation class ObsoleteCoroutinesApi
+
+/**
+ * Marks declarations that are **internal** in coroutines API, which means that should not be used outside of
+ * `kotlinx.coroutines`, because their signatures and semantics will change between future releases without any
+ * warnings and without providing any migration aids.
+ */
+@Retention(value = AnnotationRetention.BINARY)
+@Experimental(level = Experimental.Level.ERROR)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY)
+public annotation class InternalCoroutinesApi
diff --git a/kotlinx-coroutines-core/common/src/Await.kt b/kotlinx-coroutines-core/common/src/Await.kt
new file mode 100644
index 00000000..b8dc2ac9
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Await.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.atomicfu.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values
+ * when all deferred computations are complete or resumes with the first thrown exception if any of computations
+ * complete exceptionally including cancellation.
+ *
+ * This function is **not** equivalent to `deferreds.map { it.await() }` which fails only when it sequentially
+ * gets to wait for the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail.
+ *
+ * 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].
+ */
+public suspend fun <T> awaitAll(vararg deferreds: Deferred<T>): List<T> =
+ if (deferreds.isEmpty()) emptyList() else AwaitAll(deferreds).await()
+
+/**
+ * Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values
+ * when all deferred computations are complete or resumes with the first thrown exception if any of computations
+ * complete exceptionally including cancellation.
+ *
+ * This function is **not** equivalent to `this.map { it.await() }` which fails only when when it sequentially
+ * gets to wait the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail.
+ *
+ * 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].
+ */
+public suspend fun <T> Collection<Deferred<T>>.awaitAll(): List<T> =
+ if (isEmpty()) emptyList() else AwaitAll(toTypedArray()).await()
+
+/**
+ * Suspends current coroutine until all given jobs are complete.
+ * This method is semantically equivalent to joining all given jobs one by one with `jobs.forEach { it.join() }`.
+ *
+ * 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].
+ */
+public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() }
+
+/**
+ * Suspends current coroutine until all given jobs are complete.
+ * This method is semantically equivalent to joining all given jobs one by one with `forEach { it.join() }`.
+ *
+ * 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].
+ */
+public suspend fun Collection<Job>.joinAll(): Unit = forEach { it.join() }
+
+private class AwaitAll<T>(private val deferreds: Array<out Deferred<T>>) {
+ private val notCompletedCount = atomic(deferreds.size)
+
+ suspend fun await(): List<T> = suspendCancellableCoroutine { cont ->
+ // Intricate dance here
+ // Step 1: Create nodes and install them as completion handlers, they may fire!
+ val nodes = Array<AwaitAllNode>(deferreds.size) { i ->
+ val deferred = deferreds[i]
+ deferred.start() // To properly await lazily started deferreds
+ AwaitAllNode(cont, deferred).apply {
+ handle = deferred.invokeOnCompletion(asHandler)
+ }
+ }
+ val disposer = DisposeHandlersOnCancel(nodes)
+ // Step 2: Set disposer to each node
+ nodes.forEach { it.disposer = disposer }
+ // Here we know that if any code the nodes complete, it will dipsose the rest
+ // Step 3: Now we can check if continuation is complete
+ if (cont.isCompleted) {
+ // it is already complete while handlers were being installed -- dispose them all
+ disposer.disposeAll()
+ } else {
+ cont.invokeOnCancellation(handler = disposer.asHandler)
+ }
+ }
+
+ private inner class DisposeHandlersOnCancel(private val nodes: Array<AwaitAllNode>) : CancelHandler() {
+ fun disposeAll() {
+ nodes.forEach { it.handle.dispose() }
+ }
+
+ override fun invoke(cause: Throwable?) { disposeAll() }
+ override fun toString(): String = "DisposeHandlersOnCancel[$nodes]"
+ }
+
+ private inner class AwaitAllNode(private val continuation: CancellableContinuation<List<T>>, job: Job) : JobNode<Job>(job) {
+ lateinit var handle: DisposableHandle
+
+ @Volatile
+ var disposer: DisposeHandlersOnCancel? = null
+
+ override fun invoke(cause: Throwable?) {
+ if (cause != null) {
+ val token = continuation.tryResumeWithException(cause)
+ if (token != null) {
+ continuation.completeResume(token)
+ // volatile read of disposer AFTER continuation is complete
+ val disposer = this.disposer
+ // and if disposer was already set (all handlers where already installed, then dispose them all)
+ if (disposer != null) disposer.disposeAll()
+ }
+ } else if (notCompletedCount.decrementAndGet() == 0) {
+ continuation.resume(deferreds.map { it.getCompleted() })
+ // Note that all deferreds are complete here, so we don't need to dispose their nodes
+ }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt
new file mode 100644
index 00000000..4c42e5e1
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Builders.common.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("BuildersKt")
+
+package kotlinx.coroutines
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.jvm.*
+
+// --------------- launch ---------------
+
+/**
+ * Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job].
+ * The coroutine is cancelled when the resulting job is [cancelled][Job.cancel].
+ *
+ * The coroutine context is inherited from a [CoroutineScope]. Additional context elements can be specified with [context] argument.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
+ * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
+ * with a corresponding [coroutineContext] element.
+ *
+ * By default, the coroutine is immediately scheduled for execution.
+ * Other start options can be specified via `start` parameter. See [CoroutineStart] for details.
+ * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,
+ * the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function
+ * and will be started implicitly on the first invocation of [join][Job.join].
+ *
+ * Uncaught exceptions in this coroutine cancel the parent job in the context by default
+ * (unless [CoroutineExceptionHandler] is explicitly specified), which means that when `launch` is used with
+ * the context of another coroutine, then any uncaught exception leads to the cancellation of the parent coroutine.
+ *
+ * See [newCoroutineContext] for a description of debugging facilities that are available for a newly created coroutine.
+ *
+ * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the coroutine code which will be invoked in the context of the provided scope.
+ **/
+public fun CoroutineScope.launch(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> Unit
+): Job {
+ val newContext = newCoroutineContext(context)
+ val coroutine = if (start.isLazy)
+ LazyStandaloneCoroutine(newContext, block) else
+ StandaloneCoroutine(newContext, active = true)
+ coroutine.start(start, coroutine, block)
+ return coroutine
+}
+
+// --------------- async ---------------
+
+/**
+ * Creates a coroutine and returns its future result as an implementation of [Deferred].
+ * The running coroutine is cancelled when the resulting deferred is [cancelled][Job.cancel].
+ * The resulting coroutine has a key difference compared with similar primitives in other languages
+ * and frameworks: it cancels the parent job (or outer scope) on failure to enforce *structured concurrency* paradigm.
+ * To change that behaviour, supervising parent ([SupervisorJob] or [supervisorScope]) can be used.
+ *
+ * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
+ * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
+ * with corresponding [coroutineContext] element.
+ *
+ * By default, the coroutine is immediately scheduled for execution.
+ * Other options can be specified via `start` parameter. See [CoroutineStart] for details.
+ * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,
+ * the resulting [Deferred] is created in _new_ state. It can be explicitly started with [start][Job.start]
+ * function and will be started implicitly on the first invocation of [join][Job.join], [await][Deferred.await] or [awaitAll].
+ *
+ * @param block the coroutine code.
+ */
+public fun <T> CoroutineScope.async(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> T
+): Deferred<T> {
+ val newContext = newCoroutineContext(context)
+ val coroutine = if (start.isLazy)
+ LazyDeferredCoroutine(newContext, block) else
+ DeferredCoroutine<T>(newContext, active = true)
+ coroutine.start(start, coroutine, block)
+ return coroutine
+}
+
+@Suppress("UNCHECKED_CAST")
+private open class DeferredCoroutine<T>(
+ parentContext: CoroutineContext,
+ active: Boolean
+) : AbstractCoroutine<T>(parentContext, 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
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (T) -> R) =
+ registerSelectClause1Internal(select, block)
+}
+
+private class LazyDeferredCoroutine<T>(
+ parentContext: CoroutineContext,
+ block: suspend CoroutineScope.() -> T
+) : DeferredCoroutine<T>(parentContext, active = false) {
+ private var block: (suspend CoroutineScope.() -> T)? = block
+
+ override fun onStart() {
+ val block = checkNotNull(this.block) { "Already started" }
+ this.block = null
+ block.startCoroutineCancellable(this, this)
+ }
+}
+
+// --------------- withContext ---------------
+
+/**
+ * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns
+ * the result.
+ *
+ * The resulting context for the [block] is derived by merging the current [coroutineContext] with the
+ * specified [context] using `coroutineContext + context` (see [CoroutineContext.plus]).
+ * This suspending function is cancellable. It immediately checks for cancellation of
+ * the resulting context and throws [CancellationException] if it is not [active][CoroutineContext.isActive].
+ *
+ * This function uses dispatcher from the new context, shifting execution of the [block] into the
+ * different thread if a new dispatcher is specified, and back to the original dispatcher
+ * when it completes. Note that the result of `withContext` invocation is
+ * dispatched into the original context in a cancellable way, 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].
+ */
+public suspend fun <T> withContext(
+ context: CoroutineContext,
+ block: suspend CoroutineScope.() -> T
+): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
+ // compute new context
+ val oldContext = uCont.context
+ val newContext = oldContext + context
+ // always check for cancellation of new context
+ newContext.checkCompletion()
+ // FAST PATH #1 -- new context is the same as the old one
+ if (newContext === oldContext) {
+ val coroutine = ScopeCoroutine(newContext, uCont) // MODE_DIRECT
+ return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
+ }
+ // FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed)
+ // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher)
+ if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
+ val coroutine = UndispatchedCoroutine(newContext, uCont) // MODE_UNDISPATCHED
+ // There are changes in the context, so this thread needs to be updated
+ withCoroutineContext(newContext, null) {
+ return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
+ }
+ }
+ // SLOW PATH -- use new dispatcher
+ val coroutine = DispatchedCoroutine(newContext, uCont) // MODE_CANCELLABLE
+ coroutine.initParentJob()
+ block.startCoroutineCancellable(coroutine, coroutine)
+ coroutine.getResult()
+}
+
+/**
+ * Calls the specified suspending block with the given [CoroutineDispatcher], suspends until it
+ * completes, and returns the result.
+ *
+ * This inline function calls [withContext].
+ */
+@ExperimentalCoroutinesApi
+public suspend inline operator fun <T> CoroutineDispatcher.invoke(
+ noinline block: suspend CoroutineScope.() -> T
+): T = withContext(this, block)
+
+// --------------- implementation ---------------
+
+private open class StandaloneCoroutine(
+ parentContext: CoroutineContext,
+ active: Boolean
+) : AbstractCoroutine<Unit>(parentContext, active) {
+ override fun handleJobException(exception: Throwable): Boolean {
+ handleCoroutineException(context, exception)
+ return true
+ }
+}
+
+private class LazyStandaloneCoroutine(
+ parentContext: CoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
+) : StandaloneCoroutine(parentContext, active = false) {
+ private var block: (suspend CoroutineScope.() -> Unit)? = block
+
+ override fun onStart() {
+ val block = checkNotNull(this.block) { "Already started" }
+ this.block = null
+ block.startCoroutineCancellable(this, this)
+ }
+}
+
+// Used by withContext when context changes, but dispatcher stays the same
+private class UndispatchedCoroutine<in T>(
+ context: CoroutineContext,
+ uCont: Continuation<T>
+) : ScopeCoroutine<T>(context, uCont) {
+ override val defaultResumeMode: Int get() = MODE_UNDISPATCHED
+}
+
+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>(
+ context: CoroutineContext,
+ uCont: Continuation<T>
+) : ScopeCoroutine<T>(context, uCont) {
+ override val defaultResumeMode: Int get() = MODE_CANCELLABLE
+
+ // this is copy-and-paste of a decision state machine inside AbstractionContinuation
+ // todo: we may some-how abstract it via inline class
+ private val _decision = atomic(UNDECIDED)
+
+ private fun trySuspend(): Boolean {
+ _decision.loop { decision ->
+ when (decision) {
+ UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, SUSPENDED)) return true
+ RESUMED -> return false
+ else -> error("Already suspended")
+ }
+ }
+ }
+
+ private fun tryResume(): Boolean {
+ _decision.loop { decision ->
+ when (decision) {
+ UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, RESUMED)) return true
+ SUSPENDED -> return false
+ else -> error("Already resumed")
+ }
+ }
+ }
+
+ override fun afterCompletionInternal(state: Any?, mode: Int) {
+ if (tryResume()) return // completed before getResult invocation -- bail out
+ // otherwise, getResult has already commenced, i.e. completed later or in other thread
+ super.afterCompletionInternal(state, mode)
+ }
+
+ fun getResult(): Any? {
+ if (trySuspend()) return COROUTINE_SUSPENDED
+ // otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
+ val state = this.state.unboxState()
+ if (state is CompletedExceptionally) throw state.cause
+ @Suppress("UNCHECKED_CAST")
+ return state as T
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
new file mode 100644
index 00000000..139ef040
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2016-2018 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.coroutines.intrinsics.*
+
+// --------------- cancellable continuations ---------------
+
+/**
+ * Cancellable continuation. It is _completed_ when it is resumed or cancelled.
+ * When [cancel] function is explicitly invoked, this continuation immediately resumes with [CancellationException] or
+ * with the specified cancel cause.
+ *
+ * Cancellable continuation has three states (as subset of [Job] states):
+ *
+ * | **State** | [isActive] | [isCompleted] | [isCancelled] |
+ * | ----------------------------------- | ---------- | ------------- | ------------- |
+ * | _Active_ (initial state) | `true` | `false` | `false` |
+ * | _Resumed_ (final _completed_ state) | `false` | `true` | `false` |
+ * | _Canceled_ (final _completed_ state)| `false` | `true` | `true` |
+ *
+ * Invocation of [cancel] transitions this continuation from _active_ to _cancelled_ state, while
+ * invocation of [resume] or [resumeWithException] transitions it from _active_ to _resumed_ state.
+ *
+ * A [cancelled][isCancelled] continuation implies that it is [completed][isCompleted].
+ *
+ * Invocation of [resume] or [resumeWithException] in _resumed_ state produces [IllegalStateException].
+ * Invocation of [resume] in _cancelled_ state is ignored (it is a trivial race between resume from the continuation owner and
+ * outer job cancellation and cancellation wins).
+ * Invocation of [resumeWithException] in _cancelled_ state triggers exception handling of passed exception.
+ *
+ * ```
+ * +-----------+ resume +---------+
+ * | Active | ----------> | Resumed |
+ * +-----------+ +---------+
+ * |
+ * | cancel
+ * V
+ * +-----------+
+ * | Cancelled |
+ * +-----------+
+ *
+ * ```
+ */
+public interface CancellableContinuation<in T> : Continuation<T> {
+ /**
+ * Returns `true` when this continuation is active -- it has not completed or cancelled yet.
+ */
+ public val isActive: Boolean
+
+ /**
+ * Returns `true` when this continuation has completed for any reason. A continuation
+ * that was cancelled is also considered complete.
+ */
+ public val isCompleted: Boolean
+
+ /**
+ * Returns `true` if this continuation was [cancelled][cancel].
+ *
+ * It implies that [isActive] is `false` and [isCompleted] is `true`.
+ */
+ public val isCancelled: Boolean
+
+ /**
+ * Tries to resume this continuation with a given value and returns non-null object token if it was successful,
+ * or `null` otherwise (it was already resumed or cancelled). When non-null object was returned,
+ * [completeResume] must be invoked with it.
+ *
+ * When [idempotent] is not `null`, this function performs _idempotent_ operation, so that
+ * further invocations with the same non-null reference produce the same result.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public fun tryResume(value: T, idempotent: Any? = null): Any?
+
+ /**
+ * Tries to resume this continuation with a given exception and returns non-null object token if it was successful,
+ * or `null` otherwise (it was already resumed or cancelled). When non-null object was returned,
+ * [completeResume] must be invoked with it.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public fun tryResumeWithException(exception: Throwable): Any?
+
+ /**
+ * Completes the execution of [tryResume] or [tryResumeWithException] on its non-null result.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ 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.
+ *
+ * @suppress **Deprecated**: This function is no longer used.
+ * It is left for binary compatibility with code compiled before kotlinx.coroutines 1.1.0.
+ */
+ @Deprecated(
+ level = DeprecationLevel.HIDDEN,
+ message = "This function is no longer used. " +
+ "It is left for binary compatibility with code compiled before kotlinx.coroutines 1.1.0. "
+ )
+ @InternalCoroutinesApi
+ public fun initCancellability()
+
+ /**
+ * Cancels this continuation with an optional cancellation [cause]. The result is `true` if this continuation was
+ * cancelled as a result of this invocation and `false` otherwise.
+ */
+ public fun cancel(cause: Throwable? = null): Boolean
+
+ /**
+ * Registers handler that is **synchronously** invoked once on cancellation (both regular and exceptional) of this continuation.
+ * When the continuation is already cancelled, then the handler is immediately invoked
+ * with cancellation exception. Otherwise, the handler will be invoked once on cancellation if this
+ * continuation is cancelled.
+ *
+ * Installed [handler] should not throw any exceptions.
+ * If it does, they will get caught, wrapped into [CompletionHandlerException] and
+ * processed as uncaught exception in the context of the current coroutine
+ * (see [CoroutineExceptionHandler]).
+ *
+ * At most one [handler] can be installed on one continuation.
+ *
+ * **Note**: Implementation of `CompletionHandler` must be fast, non-blocking, and thread-safe.
+ * This handler can be invoked concurrently with the surrounding code.
+ * There is no guarantee on the execution context in which the [handler] is invoked.
+ */
+ public fun invokeOnCancellation(handler: CompletionHandler)
+
+ /**
+ * Resumes this continuation with a given [value] in the invoker thread without going though
+ * [dispatch][CoroutineDispatcher.dispatch] function of the [CoroutineDispatcher] in the [context].
+ * This function is designed to be used only by the [CoroutineDispatcher] implementations themselves.
+ * **It should not be used in general code**.
+ *
+ * **Note: This function is experimental.** Its signature general code may be changed in the future.
+ */
+ @ExperimentalCoroutinesApi
+ public fun CoroutineDispatcher.resumeUndispatched(value: T)
+
+ /**
+ * Resumes this continuation with a given [exception] in the invoker thread without going though
+ * [dispatch][CoroutineDispatcher.dispatch] function of the [CoroutineDispatcher] in the [context].
+ * This function is designed to be used only by the [CoroutineDispatcher] implementations themselves.
+ * **It should not be used in general code**.
+ *
+ * **Note: This function is experimental.** Its signature general code may be changed in the future.
+ */
+ @ExperimentalCoroutinesApi
+ public fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable)
+
+ /**
+ * Resumes this continuation with a given [value] and calls the specified [onCancellation]
+ * handler when resumed too late (when continuation was already cancelled) or when resumed
+ * successfully (before cancellation), but coroutine's job was cancelled before it had a
+ * chance to run in its dispatcher, so that suspended function threw an exception
+ * instead of returning this value.
+ *
+ * Installed [onCancellation] handler should not throw any exceptions.
+ * If it does, they will get caught, wrapped into [CompletionHandlerException] and
+ * processed as uncaught exception in the context of the current coroutine
+ * (see [CoroutineExceptionHandler]).
+ *
+ * This function shall be used when resuming with a resource that must be closed by the
+ * code that had called the corresponding suspending function, e.g.:
+ *
+ * ```
+ * continuation.resume(resource) {
+ * resource.close()
+ * }
+ * ```
+ *
+ * **Note**: Implementation of [onCancellation] handler must be fast, non-blocking, and thread-safe.
+ * This handler can be invoked concurrently with the surrounding code.
+ * There is no guarantee on the execution context in which the [onCancellation] handler is invoked.
+ */
+ @ExperimentalCoroutinesApi // since 1.2.0, tentatively graduates in 1.3.0
+ public fun resume(value: T, onCancellation: (cause: Throwable) -> Unit)
+}
+
+/**
+ * Suspends coroutine similar to [suspendCoroutine], but provide an implementation of [CancellableContinuation] to
+ * the [block]. This function throws [CancellationException] if the coroutine is cancelled or completed while suspended.
+ */
+public suspend inline fun <T> suspendCancellableCoroutine(
+ crossinline block: (CancellableContinuation<T>) -> Unit
+): T =
+ suspendCoroutineUninterceptedOrReturn { uCont ->
+ val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
+ // NOTE: Before version 1.1.0 the following invocation was inlined here, so invocation of this
+ // method indicates that the code was compiled by kotlinx.coroutines < 1.1.0
+ // cancellable.initCancellability()
+ block(cancellable)
+ cancellable.getResult()
+ }
+
+/**
+ * Suspends coroutine similar to [suspendCancellableCoroutine], but with *atomic cancellation*.
+ *
+ * When suspended function throws [CancellationException] it means that the continuation was not resumed.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when the continuation
+ * was already resumed and was posted for execution to the thread's queue.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public suspend inline fun <T> suspendAtomicCancellableCoroutine(
+ crossinline block: (CancellableContinuation<T>) -> Unit
+): T =
+ suspendCoroutineUninterceptedOrReturn { uCont ->
+ val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_ATOMIC_DEFAULT)
+ block(cancellable)
+ cancellable.getResult()
+ }
+
+/**
+ * @suppress **Deprecated**
+ */
+@Deprecated(
+ message = "holdCancellability parameter is deprecated and is no longer used",
+ replaceWith = ReplaceWith("suspendAtomicCancellableCoroutine(block)")
+)
+@InternalCoroutinesApi
+public suspend inline fun <T> suspendAtomicCancellableCoroutine(
+ holdCancellability: Boolean = false,
+ crossinline block: (CancellableContinuation<T>) -> Unit
+): T =
+ suspendAtomicCancellableCoroutine(block)
+
+/**
+ * Removes a given node on cancellation.
+ */
+internal fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode) =
+ invokeOnCancellation(handler = RemoveOnCancel(node).asHandler)
+
+/**
+ * Disposes a specified [handle] when this continuation is cancelled.
+ *
+ * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
+ * ```
+ * invokeOnCancellation { handle.dispose() }
+ * ```
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public fun CancellableContinuation<*>.disposeOnCancellation(handle: DisposableHandle) =
+ invokeOnCancellation(handler = DisposeOnCancel(handle).asHandler)
+
+// --------------- implementation details ---------------
+
+private class RemoveOnCancel(private val node: LockFreeLinkedListNode) : CancelHandler() {
+ override fun invoke(cause: Throwable?) { node.remove() }
+ override fun toString() = "RemoveOnCancel[$node]"
+}
+
+private class DisposeOnCancel(private val handle: DisposableHandle) : CancelHandler() {
+ override fun invoke(cause: Throwable?) = handle.dispose()
+ override fun toString(): String = "DisposeOnCancel[$handle]"
+}
diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
new file mode 100644
index 00000000..40344c9c
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2016-2019 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.internal.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.jvm.*
+
+private const val UNDECIDED = 0
+private const val SUSPENDED = 1
+private const val RESUMED = 2
+
+/**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+@PublishedApi
+internal open class CancellableContinuationImpl<in T>(
+ final override val delegate: Continuation<T>,
+ resumeMode: Int
+) : DispatchedTask<T>(resumeMode), CancellableContinuation<T>, CoroutineStackFrame {
+ public override val context: CoroutineContext = delegate.context
+
+ /*
+ * Implementation notes
+ *
+ * AbstractContinuation is a subset of Job with following limitations:
+ * 1) It can have only cancellation listeners
+ * 2) It always invokes cancellation listener if it's cancelled (no 'invokeImmediately')
+ * 3) It can have at most one cancellation listener
+ * 4) Its cancellation listeners cannot be deregistered
+ * As a consequence it has much simpler state machine, more lightweight machinery and
+ * less dependencies.
+ */
+
+ /* decision state machine
+
+ +-----------+ trySuspend +-----------+
+ | UNDECIDED | -------------> | SUSPENDED |
+ +-----------+ +-----------+
+ |
+ | tryResume
+ V
+ +-----------+
+ | RESUMED |
+ +-----------+
+
+ Note: both tryResume and trySuspend can be invoked at most once, first invocation wins
+ */
+ private val _decision = atomic(UNDECIDED)
+
+ /*
+ === Internal states ===
+ name state class public state description
+ ------ ------------ ------------ -----------
+ ACTIVE Active : Active active, no listeners
+ SINGLE_A CancelHandler : Active active, one cancellation listener
+ CANCELLED CancelledContinuation: Cancelled cancelled (final state)
+ COMPLETED any : Completed produced some result or threw an exception (final state)
+ */
+ private val _state = atomic<Any?>(Active)
+
+ @Volatile
+ private var parentHandle: DisposableHandle? = null
+
+ internal val state: Any? get() = _state.value
+
+ public override val isActive: Boolean get() = state is NotCompleted
+
+ public override val isCompleted: Boolean get() = state !is NotCompleted
+
+ public override val isCancelled: Boolean get() = state is CancelledContinuation
+
+ public override fun initCancellability() {
+ // This method does nothing. Leftover for binary compatibility with old compiled code
+ }
+
+ // It is only invoked from an internal getResult function, so we can be sure it is not invoked twice
+ private fun installParentCancellationHandler() {
+ if (isCompleted) return // fast path 1 -- don't need to do anything if already completed
+ val parent = delegate.context[Job] ?: return // fast path 2 -- don't do anything without parent
+ parent.start() // make sure the parent is started
+ 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)
+ if (isCompleted) {
+ handle.dispose() // it is Ok to call dispose twice -- here and in disposeParentHandle
+ parentHandle = NonDisposableHandle // release it just in case, to aid GC
+ }
+ }
+
+ public override val callerFrame: CoroutineStackFrame?
+ get() = delegate as? CoroutineStackFrame
+
+ public override fun getStackTraceElement(): StackTraceElement? = null
+
+ override fun takeState(): Any? = state
+
+ override fun cancelResult(state: Any?, cause: Throwable) {
+ if (state is CompletedWithCancellation) {
+ invokeHandlerSafely {
+ state.onCancellation(cause)
+ }
+ }
+ }
+
+ public override fun cancel(cause: Throwable?): Boolean {
+ _state.loop { state ->
+ if (state !is NotCompleted) return false // false if already complete or cancelling
+ // Active -- update to final state
+ val update = CancelledContinuation(this, cause, handled = state is CancelHandler)
+ if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
+ // Invoke cancel handler if it was present
+ if (state is CancelHandler) invokeHandlerSafely { state.invoke(cause) }
+ // Complete state update
+ disposeParentHandle()
+ dispatchResume(mode = MODE_ATOMIC_DEFAULT)
+ return true
+ }
+ }
+
+ private inline fun invokeHandlerSafely(block: () -> Unit) {
+ try {
+ block()
+ } catch (ex: Throwable) {
+ // Handler should never fail, if it does -- it is an unhandled exception
+ handleCoroutineException(
+ context,
+ CompletionHandlerException("Exception in cancellation handler for $this", ex)
+ )
+ }
+ }
+
+ /**
+ * It is used when parent is cancelled to get the cancellation cause for this continuation.
+ */
+ open fun getContinuationCancellationCause(parent: Job): Throwable =
+ parent.getCancellationException()
+
+ private fun trySuspend(): Boolean {
+ _decision.loop { decision ->
+ when (decision) {
+ UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, SUSPENDED)) return true
+ RESUMED -> return false
+ else -> error("Already suspended")
+ }
+ }
+ }
+
+ private fun tryResume(): Boolean {
+ _decision.loop { decision ->
+ when (decision) {
+ UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, RESUMED)) return true
+ SUSPENDED -> return false
+ else -> error("Already resumed")
+ }
+ }
+ }
+
+ @PublishedApi
+ internal fun getResult(): Any? {
+ installParentCancellationHandler()
+ if (trySuspend()) return COROUTINE_SUSPENDED
+ // otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
+ 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
+ // otherwise, there is a race is suspendCancellableCoroutine { cont -> ... } does cont.resume(...)
+ // before the block returns. This getResult would return a result as opposed to cancellation
+ // exception that should have happened if the continuation is dispatched for execution later.
+ if (resumeMode == MODE_CANCELLABLE) {
+ val job = context[Job]
+ if (job != null && !job.isActive) {
+ val cause = job.getCancellationException()
+ cancelResult(state, cause)
+ throw recoverStackTrace(cause, this)
+ }
+ }
+ return getSuccessfulResult(state)
+ }
+
+ override fun resumeWith(result: Result<T>) {
+ resumeImpl(result.toState(), resumeMode)
+ }
+
+ override fun resume(value: T, onCancellation: (cause: Throwable) -> Unit) {
+ val cancelled = resumeImpl(CompletedWithCancellation(value, onCancellation), resumeMode)
+ if (cancelled != null) {
+ // too late to resume (was cancelled) -- call handler
+ invokeHandlerSafely {
+ onCancellation(cancelled.cause)
+ }
+ }
+ }
+
+ internal fun resumeWithExceptionMode(exception: Throwable, mode: Int) =
+ resumeImpl(CompletedExceptionally(exception), mode)
+
+ public override fun invokeOnCancellation(handler: CompletionHandler) {
+ var handleCache: CancelHandler? = null
+ _state.loop { state ->
+ when (state) {
+ is Active -> {
+ val node = handleCache ?: makeHandler(handler).also { handleCache = it }
+ if (_state.compareAndSet(state, node)) return // quit on cas success
+ }
+ is CancelHandler -> multipleHandlersError(handler, state)
+ is CancelledContinuation -> {
+ /*
+ * Continuation was already cancelled, invoke directly.
+ * NOTE: multiple invokeOnCancellation calls with different handlers are not allowed,
+ * so we check to make sure that handler was installed just once.
+ */
+ if (!state.makeHandled()) multipleHandlersError(handler, state)
+ /*
+ * :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension,
+ * because we play type tricks on Kotlin/JS and handler is not necessarily a function there
+ */
+ invokeHandlerSafely { handler.invokeIt((state as? CompletedExceptionally)?.cause) }
+ return
+ }
+ else -> {
+ /*
+ * Continuation was already completed, do nothing.
+ * NOTE: multiple invokeOnCancellation calls with different handlers are not allowed,
+ * but we have no way to check that it was installed just once in this case.
+ */
+ return
+ }
+ }
+ }
+ }
+
+ private fun multipleHandlersError(handler: CompletionHandler, state: Any?) {
+ error("It's prohibited to register multiple handlers, tried to register $handler, already has $state")
+ }
+
+ private fun makeHandler(handler: CompletionHandler): CancelHandler =
+ if (handler is CancelHandler) handler else InvokeOnCancel(handler)
+
+ private fun dispatchResume(mode: Int) {
+ if (tryResume()) return // completed before getResult invocation -- bail out
+ // otherwise, getResult has already commenced, i.e. completed later or in other thread
+ dispatch(mode)
+ }
+
+ // returns null when successfully dispatched resumed, CancelledContinuation if too late (was already cancelled)
+ private fun resumeImpl(proposedUpdate: Any?, resumeMode: Int): CancelledContinuation? {
+ _state.loop { state ->
+ when (state) {
+ is NotCompleted -> {
+ if (!_state.compareAndSet(state, proposedUpdate)) return@loop // retry on cas failure
+ disposeParentHandle()
+ dispatchResume(resumeMode)
+ return null
+ }
+ is CancelledContinuation -> {
+ /*
+ * If continuation was cancelled, then resume attempt must be ignored,
+ * because cancellation is asynchronous and may race with resume.
+ * Racy exceptions will be lost, too.
+ */
+ if (state.makeResumed()) return state // tried to resume just once, but was cancelled
+ }
+ }
+ alreadyResumedError(proposedUpdate) // otherwise -- an error (second resume attempt)
+ }
+ }
+
+ private fun alreadyResumedError(proposedUpdate: Any?) {
+ error("Already resumed, but proposed with update $proposedUpdate")
+ }
+
+ // Unregister from parent job
+ private fun disposeParentHandle() {
+ parentHandle?.let { // volatile read parentHandle (once)
+ it.dispose()
+ parentHandle = NonDisposableHandle // release it just in case, to aid GC
+ }
+ }
+
+ override fun tryResume(value: T, idempotent: Any?): Any? {
+ _state.loop { state ->
+ when (state) {
+ is NotCompleted -> {
+ val update: Any? = if (idempotent == null) value else
+ CompletedIdempotentResult(idempotent, value, state)
+ if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
+ disposeParentHandle()
+ return state
+ }
+ is CompletedIdempotentResult -> {
+ return if (state.idempotentResume === idempotent) {
+ assert { state.result === value } // "Non-idempotent resume"
+ state.token
+ } else {
+ null
+ }
+ }
+ else -> return null // cannot resume -- not active anymore
+ }
+ }
+ }
+
+ override fun tryResumeWithException(exception: Throwable): Any? {
+ _state.loop { state ->
+ when (state) {
+ is NotCompleted -> {
+ val update = CompletedExceptionally(exception)
+ if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
+ disposeParentHandle()
+ return state
+ }
+ else -> return null // cannot resume -- not active anymore
+ }
+ }
+ }
+
+ override fun completeResume(token: Any) {
+ // note: We don't actually use token anymore, because handler needs to be invoked on cancellation only
+ dispatchResume(resumeMode)
+ }
+
+ override fun CoroutineDispatcher.resumeUndispatched(value: T) {
+ val dc = delegate as? DispatchedContinuation
+ resumeImpl(value, if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode)
+ }
+
+ override fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable) {
+ val dc = delegate as? DispatchedContinuation
+ resumeImpl(CompletedExceptionally(exception), if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T> getSuccessfulResult(state: Any?): T =
+ when (state) {
+ is CompletedIdempotentResult -> state.result as T
+ is CompletedWithCancellation -> state.result as T
+ else -> state as T
+ }
+
+ // For nicer debugging
+ public override fun toString(): String =
+ "${nameString()}(${delegate.toDebugString()}){$state}@$hexAddress"
+
+ protected open fun nameString(): String =
+ "CancellableContinuation"
+
+}
+
+// Marker for active continuation
+internal interface NotCompleted
+
+private object Active : NotCompleted {
+ override fun toString(): String = "Active"
+}
+
+internal abstract class CancelHandler : CancelHandlerBase(), NotCompleted
+
+// Wrapper for lambdas, for the performance sake CancelHandler can be subclassed directly
+private class InvokeOnCancel( // Clashes with InvokeOnCancellation
+ private val handler: CompletionHandler
+) : CancelHandler() {
+ override fun invoke(cause: Throwable?) {
+ handler.invoke(cause)
+ }
+ override fun toString() = "InvokeOnCancel[${handler.classSimpleName}@$hexAddress]"
+}
+
+private class CompletedIdempotentResult(
+ @JvmField val idempotentResume: Any?,
+ @JvmField val result: Any?,
+ @JvmField val token: NotCompleted
+) {
+ override fun toString(): String = "CompletedIdempotentResult[$result]"
+}
+
+private class CompletedWithCancellation(
+ @JvmField val result: Any?,
+ @JvmField val onCancellation: (cause: Throwable) -> Unit
+) {
+ override fun toString(): String = "CompletedWithCancellation[$result]"
+}
+
diff --git a/kotlinx-coroutines-core/common/src/CompletableDeferred.kt b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt
new file mode 100644
index 00000000..3ad7236b
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("DEPRECATION_ERROR")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.selects.*
+
+/**
+ * A [Deferred] that can be completed via public functions [complete] or [cancel][Job.cancel].
+ *
+ * Note that the [complete] function returns `false` when this deferred value is already complete or completing,
+ * while [cancel][Job.cancel] returns `true` as long as the deferred is still _cancelling_ and the corresponding
+ * exception is incorporated into the final [completion exception][getCompletionExceptionOrNull].
+ *
+ * An instance of completable deferred can be created by `CompletableDeferred()` function in _active_ state.
+ *
+ * All functions on this interface and on all interfaces derived from it are **thread-safe** and can
+ * be safely invoked from concurrent coroutines without external synchronization.
+ */
+public interface CompletableDeferred<T> : Deferred<T> {
+ /**
+ * Completes this deferred value with a given [value]. The result is `true` if this deferred was
+ * completed as a result of this invocation and `false` otherwise (if it was already completed).
+ *
+ * Subsequent invocations of this function have no effect and always produce `false`.
+ *
+ * This function transitions this deferred into _completed_ state if it was not completed or cancelled yet.
+ * However, if this deferred has children, then it transitions into _completing_ state and becomes _complete_
+ * once all its children are [complete][isCompleted]. See [Job] for details.
+ */
+ public fun complete(value: T): Boolean
+
+ /**
+ * Completes this deferred value exceptionally with a given [exception]. The result is `true` if this deferred was
+ * completed as a result of this invocation and `false` otherwise (if it was already completed).
+ *
+ * Subsequent invocations of this function have no effect and always produce `false`.
+ *
+ * This function transitions this deferred into _cancelled_ state if it was not completed or cancelled yet.
+ * However, that if this deferred has children, then it transitions into _cancelling_ state and becomes _cancelled_
+ * once all its children are [complete][isCompleted]. See [Job] for details.
+ */
+ public fun completeExceptionally(exception: Throwable): Boolean
+}
+
+/**
+ * Creates a [CompletableDeferred] in an _active_ state.
+ * It is optionally a child of a [parent] job.
+ */
+@Suppress("FunctionName")
+public fun <T> CompletableDeferred(parent: Job? = null): CompletableDeferred<T> = CompletableDeferredImpl(parent)
+
+/**
+ * Creates an already _completed_ [CompletableDeferred] with a given [value].
+ */
+@Suppress("FunctionName")
+public fun <T> CompletableDeferred(value: T): CompletableDeferred<T> = CompletableDeferredImpl<T>(null).apply { complete(value) }
+
+/**
+ * Concrete implementation of [CompletableDeferred].
+ */
+@Suppress("UNCHECKED_CAST")
+private class CompletableDeferredImpl<T>(
+ parent: Job?
+) : JobSupport(true), CompletableDeferred<T>, SelectClause1<T> {
+ init { initParentJobInternal(parent) }
+ override val onCancelComplete get() = true
+ override fun getCompleted(): T = getCompletedInternal() as T
+ override suspend fun await(): T = awaitInternal() as T
+ override val onAwait: SelectClause1<T> get() = this
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (T) -> R) =
+ registerSelectClause1Internal(select, block)
+
+ override fun complete(value: T): Boolean =
+ makeCompleting(value)
+ override fun completeExceptionally(exception: Throwable): Boolean =
+ makeCompleting(CompletedExceptionally(exception))
+}
diff --git a/kotlinx-coroutines-core/common/src/CompletableJob.kt b/kotlinx-coroutines-core/common/src/CompletableJob.kt
new file mode 100644
index 00000000..f8187494
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CompletableJob.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * A job that can be completed using [complete()] function.
+ * It is returned by [Job()][Job] and [SupervisorJob()][SupervisorJob] constructor functions.
+ */
+public interface CompletableJob : Job {
+ /**
+ * Completes this job. The result is `true` if this job was completed as a result of this invocation and
+ * `false` otherwise (if it was already completed).
+ *
+ * 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.
+ * 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.
+ */
+ public fun complete(): Boolean
+
+ /**
+ * Completes this job exceptionally with a given [exception]. The result is `true` if this job was
+ * completed as a result of this invocation and `false` otherwise (if it was already completed).
+ * [exception] parameter is used as an additional debug information that is not handled by any exception handlers.
+ *
+ * Subsequent invocations of this function have no effect and always produce `false`.
+ *
+ * This function transitions this job into _cancelled_ state if it was not completed or cancelled yet.
+ * However, that if this job has children, then it transitions into _cancelling_ state and becomes _cancelled_
+ * once all its children are [complete][isCompleted]. See [Job] for details.
+ */
+ public fun completeExceptionally(exception: Throwable): Boolean
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/CompletedExceptionally.kt b/kotlinx-coroutines-core/common/src/CompletedExceptionally.kt
new file mode 100644
index 00000000..d15c8575
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CompletedExceptionally.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.atomicfu.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+internal fun <T> Result<T>.toState(): Any? =
+ if (isSuccess) getOrThrow() else CompletedExceptionally(exceptionOrNull()!!) // todo: need to do it better
+
+/**
+ * Class for an internal state of a job that was cancelled (completed exceptionally).
+ *
+ * @param cause the exceptional completion cause. It's either original exceptional cause
+ * or artificial [CancellationException] if no cause was provided
+ */
+internal open class CompletedExceptionally(
+ @JvmField public val cause: Throwable,
+ handled: Boolean = false
+) {
+ private val _handled = atomic(handled)
+ val handled: Boolean get() = _handled.value
+ fun makeHandled(): Boolean = _handled.compareAndSet(false, true)
+ override fun toString(): String = "$classSimpleName[$cause]"
+}
+
+/**
+ * A specific subclass of [CompletedExceptionally] for cancelled [AbstractContinuation].
+ *
+ * @param continuation the continuation that was cancelled.
+ * @param cause the exceptional completion cause. If `cause` is null, then a [CancellationException]
+ * if created on first access to [exception] property.
+ */
+internal class CancelledContinuation(
+ continuation: Continuation<*>,
+ cause: Throwable?,
+ handled: Boolean
+) : CompletedExceptionally(cause ?: CancellationException("Continuation $continuation was cancelled normally"), handled) {
+ private val _resumed = atomic(false)
+ fun makeResumed(): Boolean = _resumed.compareAndSet(false, true)
+}
diff --git a/kotlinx-coroutines-core/common/src/CompletionHandler.common.kt b/kotlinx-coroutines-core/common/src/CompletionHandler.common.kt
new file mode 100644
index 00000000..00bac305
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CompletionHandler.common.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+
+/**
+ * Handler for [Job.invokeOnCompletion] and [CancellableContinuation.invokeOnCancellation].
+ *
+ * Installed handler should not throw any exceptions. If it does, they will get caught,
+ * wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code.
+ *
+ * The meaning of `cause` that is passed to the handler:
+ * * Cause is `null` when the job has completed normally.
+ * * Cause is an instance of [CancellationException] when the job was cancelled _normally_.
+ * **It should not be treated as an error**. In particular, it should not be reported to error logs.
+ * * Otherwise, the job had _failed_.
+ *
+ * **Note**: This type is a part of internal machinery that supports parent-child hierarchies
+ * and allows for implementation of suspending functions that wait on the Job's state.
+ * This type should not be used in general application code.
+ * Implementations of `CompletionHandler` must be fast and _lock-free_.
+ */
+public typealias CompletionHandler = (cause: Throwable?) -> Unit
+
+// We want class that extends LockFreeLinkedListNode & CompletionHandler but we cannot do it on Kotlin/JS,
+// so this expect class provides us with the corresponding abstraction in a platform-agnostic way.
+internal expect abstract class CompletionHandlerBase() : LockFreeLinkedListNode {
+ abstract fun invoke(cause: Throwable?)
+}
+
+internal expect val CompletionHandlerBase.asHandler: CompletionHandler
+
+// More compact version of CompletionHandlerBase for CancellableContinuation with same workaround for JS
+internal expect abstract class CancelHandlerBase() {
+ abstract fun invoke(cause: Throwable?)
+}
+
+internal expect val CancelHandlerBase.asHandler: CompletionHandler
+
+// :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension,
+// because we play type tricks on Kotlin/JS and handler is not necessarily a function there
+internal expect fun CompletionHandler.invokeIt(cause: Throwable?)
+
+internal inline fun <reified T> CompletionHandler.isHandlerOf(): Boolean = this is T
diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
new file mode 100644
index 00000000..785e8a76
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+/**
+ * Creates context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher nor
+ * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on).
+ */
+public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext
+
+internal expect fun createDefaultDispatcher(): CoroutineDispatcher
+
+@Suppress("PropertyName")
+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 fun Continuation<*>.toDebugString(): String
+internal expect val CoroutineContext.coroutineName: String? \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
new file mode 100644
index 00000000..19308c21
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+/**
+ * Base class that shall be extended by all coroutine dispatcher implementations.
+ *
+ * The following standard implementations are provided by `kotlinx.coroutines` as properties on
+ * [Dispatchers] objects:
+ *
+ * * [Dispatchers.Default] -- is used by all standard builder if no dispatcher nor any other [ContinuationInterceptor]
+ * is specified in their context. It uses a common pool of shared background threads.
+ * This is an appropriate choice for compute-intensive coroutines that consume CPU resources.
+ * * [Dispatchers.IO] -- uses a shared pool of on-demand created threads and is designed for offloading of IO-intensive _blocking_
+ * operations (like file I/O and blocking socket I/O).
+ * * [Dispatchers.Unconfined] -- starts coroutine execution in the current call-frame until the first suspension.
+ * On first suspension the coroutine builder function returns.
+ * The coroutine resumes in whatever thread that is used by the
+ * corresponding suspending function, without confining it to any specific thread or pool.
+ * **Unconfined dispatcher should not be normally used in code**.
+ * * Private thread pools can be created with [newSingleThreadContext] and [newFixedThreadPoolContext].
+ * * An arbitrary [Executor][java.util.concurrent.Executor] can be converted to dispatcher with [asCoroutineDispatcher] extension function.
+ *
+ * This class ensures that debugging facilities in [newCoroutineContext] function work properly.
+ */
+public abstract class CoroutineDispatcher :
+ AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
+ /**
+ * Returns `true` if execution shall be dispatched onto another thread.
+ * The default behaviour for most dispatchers is to return `true`.
+ *
+ * This method should never be used from general code, it is used only by `kotlinx.coroutines`
+ * internals and its contract with the rest of API is an implementation detail.
+ *
+ * UI dispatchers _should not_ override `isDispatchNeeded`, but leave a default implementation that
+ * returns `true`. To understand the rationale beyond this recommendation, consider the following code:
+ *
+ * ```kotlin
+ * fun asyncUpdateUI() = async(Dispatchers.Main) {
+ * // do something here that updates something in UI
+ * }
+ * ```
+ *
+ * When you invoke `asyncUpdateUI` in some background thread, it immediately continues to the next
+ * line, while UI update happens asynchronously in the UI thread. However, if you invoke
+ * it in the UI thread itself, it updates UI _synchronously_ if your `isDispatchNeeded` is
+ * overridden with a thread check. Checking if we are already in the UI thread seems more
+ * efficient (and it might indeed save a few CPU cycles), but this subtle and context-sensitive
+ * difference in behavior makes the resulting async code harder to debug.
+ *
+ * Basically, the choice here is between "JS-style" asynchronous approach (async actions
+ * are always postponed to be executed later in the even dispatch thread) and "C#-style" approach
+ * (async actions are executed in the invoker thread until the first suspension point).
+ * While, C# approach seems to be more efficient, it ends up with recommendations like
+ * "use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent
+ * and does not require programmers to think about whether they need to yield or not.
+ *
+ * However, coroutine builders like [launch][CoroutineScope.launch] and [async][CoroutineScope.async] accept an optional [CoroutineStart]
+ * parameter that allows one to optionally choose C#-style [CoroutineStart.UNDISPATCHED] behaviour
+ * whenever it is needed for efficiency.
+ *
+ * This method should be generally exception-safe, an exception thrown from this method
+ * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
+ *
+ * **Note: This is an experimental api.** Execution semantics of coroutines may change in the future when this function returns `false`.
+ */
+ @ExperimentalCoroutinesApi
+ public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
+
+ /**
+ * Dispatches execution of a runnable [block] onto another thread in the given [context].
+ *
+ * This method should be generally exception-safe, an exception thrown from this method
+ * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
+ */
+ public abstract fun dispatch(context: CoroutineContext, block: Runnable)
+
+ /**
+ * Dispatches execution of a runnable [block] onto another thread in the given [context]
+ * with a hint for dispatcher that current dispatch is triggered by [yield] call, so execution of this
+ * continuation may be delayed in favor of already dispatched coroutines.
+ *
+ * **Implementation note** though yield marker may be passed as a part of [context], this
+ * is a separate method for performance reasons
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ public open fun dispatchYield(context: CoroutineContext, block: Runnable) = dispatch(context, block)
+
+ /**
+ * Returns continuation that wraps the original [continuation], thus intercepting all resumptions.
+ *
+ * This method should be generally exception-safe, an exception thrown from this method
+ * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
+ */
+ public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
+ DispatchedContinuation(this, continuation)
+
+ /**
+ * @suppress **Error**: Operator '+' on two CoroutineDispatcher objects is meaningless.
+ * CoroutineDispatcher is a coroutine context element and `+` is a set-sum operator for coroutine contexts.
+ * The dispatcher to the right of `+` just replaces the dispatcher the left of `+`.
+ */
+ @Suppress("DeprecatedCallableAddReplaceWith")
+ @Deprecated(
+ message = "Operator '+' on two CoroutineDispatcher objects is meaningless. " +
+ "CoroutineDispatcher is a coroutine context element and `+` is a set-sum operator for coroutine contexts. " +
+ "The dispatcher to the right of `+` just replaces the dispatcher the left of `+`.",
+ level = DeprecationLevel.ERROR
+ )
+ public operator fun plus(other: CoroutineDispatcher) = other
+
+ /** @suppress for nicer debugging */
+ override fun toString(): String = "$classSimpleName@$hexAddress"
+}
+
diff --git a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
new file mode 100644
index 00000000..eca095f8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable)
+
+/**
+ * Helper function for coroutine builder implementations to handle uncaught and unexpected exceptions in coroutines,
+ * that could not be otherwise handled in a normal way through structured concurrency, saving them to a future, and
+ * cannot be rethrown. This is a last resort handler to prevent lost exceptions.
+ *
+ * If there is [CoroutineExceptionHandler] in the context, then it is used. If it throws an exception during handling
+ * or is absent, all instances of [CoroutineExceptionHandler] found via [ServiceLoader] and
+ * [Thread.uncaughtExceptionHandler] are invoked.
+ */
+@InternalCoroutinesApi
+public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
+ // Invoke an exception handler from the context if present
+ try {
+ context[CoroutineExceptionHandler]?.let {
+ it.handleException(context, exception)
+ return
+ }
+ } catch (t: Throwable) {
+ handleCoroutineExceptionImpl(context, handlerException(exception, t))
+ return
+ }
+ // If a handler is not present in the context or an exception was thrown, fallback to the global handler
+ handleCoroutineExceptionImpl(context, exception)
+}
+
+internal fun handlerException(originalException: Throwable, thrownException: Throwable): Throwable {
+ if (originalException === thrownException) return originalException
+ return RuntimeException("Exception while trying to handle coroutine exception", thrownException).apply {
+ addSuppressedThrowable(originalException)
+ }
+}
+
+/**
+ * Creates a [CoroutineExceptionHandler] instance.
+ * @param handler a function which handles exception thrown by a coroutine
+ */
+@Suppress("FunctionName")
+public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler =
+ object : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler {
+ override fun handleException(context: CoroutineContext, exception: Throwable) =
+ handler.invoke(context, exception)
+ }
+
+/**
+ * An optional element in the coroutine context to handle uncaught exceptions.
+ *
+ * Normally, uncaught exceptions can only result from coroutines created using the [launch][CoroutineScope.launch] builder.
+ * A coroutine that was created using [async][CoroutineScope.async] always catches all its exceptions and represents them
+ * in the resulting [Deferred] object.
+ *
+ * By default, when no handler is installed, uncaught exception are handled in the following way:
+ * * If exception is [CancellationException] then it is ignored
+ * (because that is the supposed mechanism to cancel the running coroutine)
+ * * Otherwise:
+ * * if there is a [Job] in the context, then [Job.cancel] is invoked;
+ * * Otherwise, all instances of [CoroutineExceptionHandler] found via [ServiceLoader]
+ * * and current thread's [Thread.uncaughtExceptionHandler] are invoked.
+ **/
+public interface CoroutineExceptionHandler : CoroutineContext.Element {
+ /**
+ * Key for [CoroutineExceptionHandler] instance in the coroutine context.
+ */
+ public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
+
+ /**
+ * Handles uncaught [exception] in the given [context]. It is invoked
+ * if coroutine has an uncaught exception.
+ */
+ public fun handleException(context: CoroutineContext, exception: Throwable)
+}
diff --git a/kotlinx-coroutines-core/common/src/CoroutineName.kt b/kotlinx-coroutines-core/common/src/CoroutineName.kt
new file mode 100644
index 00000000..4a7e9ea4
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CoroutineName.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.AbstractCoroutineContextElement
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * User-specified name of coroutine. This name is used in debugging mode.
+ * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for the description of coroutine debugging facilities.
+ */
+public data class CoroutineName(
+ /**
+ * User-defined coroutine name.
+ */
+ val name: String
+) : AbstractCoroutineContextElement(CoroutineName) {
+ /**
+ * Key for [CoroutineName] instance in the coroutine context.
+ */
+ public companion object Key : CoroutineContext.Key<CoroutineName>
+
+ /**
+ * Returns a string representation of the object.
+ */
+ override fun toString(): String = "CoroutineName($name)"
+}
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
new file mode 100644
index 00000000..a9c7fb3a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2016-2018 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.intrinsics.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+/**
+ * Defines a scope for new coroutines. Every coroutine builder
+ * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
+ * to automatically propagate both context elements and cancellation.
+ *
+ * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions.
+ * Additional context elements can be appended to the scope using the [plus][CoroutineScope.plus] operator.
+ *
+ * Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead.
+ * By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a [job][Job] to enforce structured concurrency.
+ *
+ * Every coroutine builder (like [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc)
+ * and every scoping function (like [coroutineScope], [withContext], etc) provides _its own_ scope
+ * with its own [Job] instance into the inner block of code it runs.
+ * By convention, they all wait for all the coroutines inside their block to complete before completing themselves,
+ * thus enforcing the discipline of **structured concurrency**.
+ *
+ * [CoroutineScope] should be implemented (or used as a field) on entities with a well-defined lifecycle that are responsible
+ * for launching children coroutines. Example of such entity on Android is Activity.
+ * Usage of this interface may look like this:
+ *
+ * ```
+ * class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() {
+ * override fun onDestroy() {
+ * cancel() // cancel is extension on CoroutineScope
+ * }
+ *
+ * /*
+ * * Note how coroutine builders are scoped: if activity is destroyed or any of the launched coroutines
+ * * in this method throws an exception, then all nested coroutines are cancelled.
+ * */
+ * fun showSomeData() = launch { // <- extension on current activity, launched in the main thread
+ * // ... here we can use suspending functions or coroutine builders with other dispatchers
+ * draw(data) // draw in the main thread
+ * }
+ * }
+ * ```
+ */
+public interface CoroutineScope {
+ /**
+ * The context of this scope.
+ * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
+ * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
+ *
+ * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
+ */
+ public val coroutineContext: CoroutineContext
+}
+
+/**
+ * Adds the specified coroutine context to this scope, overriding existing elements in the current
+ * scope's context with the corresponding keys.
+ *
+ * This is a shorthand for `CoroutineScope(thisScope + context)`.
+ */
+public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope =
+ ContextScope(coroutineContext + context)
+
+/**
+ * Creates the main [CoroutineScope] for UI components.
+ *
+ * Example of use:
+ * ```
+ * class MyAndroidActivity {
+ * private val scope = MainScope()
+ *
+ * override fun onDestroy() {
+ * super.onDestroy()
+ * scope.cancel()
+ * }
+ * }
+ *
+ * ```
+ *
+ * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
+ * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
+ * `val scope = MainScope() + CoroutineName("MyActivity")`.
+ */
+@Suppress("FunctionName")
+public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
+
+/**
+ * Returns `true` when the current [Job] is still active (has not completed and was not cancelled yet).
+ *
+ * Check this property in long-running computation loops to support cancellation:
+ * ```
+ * while (isActive) {
+ * // do some computation
+ * }
+ * ```
+ *
+ * This property is a shortcut for `coroutineContext.isActive` in the scope when
+ * [CoroutineScope] is available.
+ * See [coroutineContext][kotlin.coroutines.coroutineContext],
+ * [isActive][kotlinx.coroutines.isActive] and [Job.isActive].
+ */
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+public val CoroutineScope.isActive: Boolean
+ get() = coroutineContext[Job]?.isActive ?: true
+
+/**
+ * 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.
+ *
+ * Usage of this interface may look like this:
+ *
+ * ```
+ * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
+ * for (number in this) {
+ * send(Math.sqrt(number))
+ * }
+ * }
+ *
+ * ```
+ */
+public object GlobalScope : CoroutineScope {
+ /**
+ * Returns [EmptyCoroutineContext].
+ */
+ override val coroutineContext: CoroutineContext
+ get() = EmptyCoroutineContext
+}
+
+/**
+ * Creates a [CoroutineScope] and calls the specified suspend block with this scope.
+ * The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
+ * the context's [Job].
+ *
+ * This function is designed for _parallel decomposition_ of work. When any child coroutine in this scope fails,
+ * this scope fails and all the rest of the children are cancelled (for a different behavior see [supervisorScope]).
+ * This function returns as soon as the given block and all its children coroutines are completed.
+ * A usage example of a scope looks like this:
+ *
+ * ```
+ * suspend fun showSomeData() = coroutineScope {
+ *
+ * val data = async(Dispatchers.IO) { // <- extension on current scope
+ * ... load some UI data for the Main thread ...
+ * }
+ *
+ * withContext(Dispatchers.Main) {
+ * doSomeWork()
+ * val result = data.await()
+ * display(result)
+ * }
+ * }
+ * ```
+ *
+ * The scope in this example has the following semantics:
+ * 1) `showSomeData` returns as soon as the data is loaded and displayed in the UI.
+ * 2) If `doSomeWork` throws an exception, then the `async` task is cancelled and `showSomeData` rethrows that exception.
+ * 3) If the outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled.
+ * 4) If the `async` block fails, `withContext` will be cancelled.
+ *
+ * The method may throw a [CancellationException] if the current job was cancelled externally
+ * or may throw a corresponding unhandled [Throwable] if there is any unhandled exception in this scope
+ * (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope).
+ */
+public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
+ suspendCoroutineUninterceptedOrReturn { uCont ->
+ val coroutine = ScopeCoroutine(uCont.context, uCont)
+ coroutine.startUndispatchedOrReturn(coroutine, block)
+ }
+
+/**
+ * Creates a [CoroutineScope] that wraps the given coroutine [context].
+ *
+ * If the given [context] does not contain a [Job] element, then a default `Job()` is created.
+ * This way, cancellation or failure or any child coroutine in this scope cancels all the other children,
+ * just like inside [coroutineScope] block.
+ */
+@Suppress("FunctionName")
+public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
+ ContextScope(if (context[Job] != null) context else context + Job())
+
+/**
+ * Cancels this scope, including its job and all its children with an optional cancellation [cause].
+ * A cause can be used to specify an error message or to provide other details on
+ * a cancellation reason for debugging purposes.
+ * Throws [IllegalStateException] if the scope does not have a job in it.
+ */
+public fun CoroutineScope.cancel(cause: CancellationException? = null) {
+ val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
+ job.cancel(cause)
+}
+
+/**
+ * Cancels this scope, including its job and all its children with a specified diagnostic error [message].
+ * A [cause] can be specified to provide additional details on a cancellation reason for debugging purposes.
+ * Throws [IllegalStateException] if the scope does not have a job in it.
+ */
+public fun CoroutineScope.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause))
+
+/**
+ * Ensures that current scope is [active][CoroutineScope.isActive].
+ * Throws [IllegalStateException] if the context does not have a job in it.
+ *
+ * If the job is no longer active, throws [CancellationException].
+ * If the job was cancelled, thrown exception contains the original cancellation cause.
+ *
+ * This method is a drop-in replacement for the following code, but with more precise exception:
+ * ```
+ * if (!isActive) {
+ * throw CancellationException()
+ * }
+ * ```
+ */
+public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()
diff --git a/kotlinx-coroutines-core/common/src/CoroutineStart.kt b/kotlinx-coroutines-core/common/src/CoroutineStart.kt
new file mode 100644
index 00000000..9e283b0d
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CoroutineStart.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.CoroutineStart.*
+import kotlinx.coroutines.intrinsics.*
+import kotlin.coroutines.*
+
+/**
+ * Defines start options for coroutines builders.
+ * It is used in `start` parameter of [launch][CoroutineScope.launch], [async][CoroutineScope.async], and other coroutine builder functions.
+ *
+ * The summary of coroutine start options is:
+ * * [DEFAULT] -- immediately schedules coroutine for execution according to its context;
+ * * [LAZY] -- starts coroutine lazily, only when it is needed;
+ * * [ATOMIC] -- atomically (in a non-cancellable way) schedules coroutine for execution according to its context;
+ * * [UNDISPATCHED] -- immediately executes coroutine until its first suspension point _in the current thread_.
+ */
+public enum class CoroutineStart {
+ /**
+ * Default -- immediately schedules the coroutine for execution according to its context.
+ *
+ * If the [CoroutineDispatcher] of the coroutine context returns `true` from [CoroutineDispatcher.isDispatchNeeded]
+ * function as most dispatchers do, then the coroutine code is dispatched for execution later, while the code that
+ * invoked the coroutine builder continues execution.
+ *
+ * Note that [Dispatchers.Unconfined] always returns `false` from its [CoroutineDispatcher.isDispatchNeeded]
+ * function, so starting a coroutine with [Dispatchers.Unconfined] by [DEFAULT] is the same as using [UNDISPATCHED].
+ *
+ * If coroutine [Job] is cancelled before it even had a chance to start executing, then it will not start its
+ * execution at all, but will complete with an exception.
+ *
+ * Cancellability of a coroutine at suspension points depends on the particular implementation details of
+ * suspending functions. Use [suspendCancellableCoroutine] to implement cancellable suspending functions.
+ */
+ DEFAULT,
+
+ /**
+ * Starts the coroutine lazily, only when it is needed.
+ *
+ * See the documentation for the corresponding coroutine builders for details
+ * (like [launch][CoroutineScope.launch] and [async][CoroutineScope.async]).
+ *
+ * If coroutine [Job] is cancelled before it even had a chance to start executing, then it will not start its
+ * execution at all, but will complete with an exception.
+ */
+ LAZY,
+
+ /**
+ * Atomically (i.e., in a non-cancellable way) schedules the coroutine for execution according to its context.
+ * This is similar to [DEFAULT], but the coroutine cannot be cancelled before it starts executing.
+ *
+ * Cancellability of coroutine at suspension points depends on the particular implementation details of
+ * suspending functions as in [DEFAULT].
+ */
+ @ExperimentalCoroutinesApi
+ 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
+ * 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,
+ * but the difference is that it starts executing in the same thread.
+ *
+ * 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.
+ */
+ @ExperimentalCoroutinesApi
+ UNDISPATCHED;
+
+ /**
+ * Starts the corresponding block as a coroutine with this coroutine's start strategy.
+ *
+ * * [DEFAULT] uses [startCoroutineCancellable].
+ * * [ATOMIC] uses [startCoroutine].
+ * * [UNDISPATCHED] uses [startCoroutineUndispatched].
+ * * [LAZY] does nothing.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ public operator fun <T> invoke(block: suspend () -> T, completion: Continuation<T>) =
+ when (this) {
+ CoroutineStart.DEFAULT -> block.startCoroutineCancellable(completion)
+ CoroutineStart.ATOMIC -> block.startCoroutine(completion)
+ CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(completion)
+ CoroutineStart.LAZY -> Unit // will start lazily
+ }
+
+ /**
+ * Starts the corresponding block with receiver as a coroutine with this coroutine start strategy.
+ *
+ * * [DEFAULT] uses [startCoroutineCancellable].
+ * * [ATOMIC] uses [startCoroutine].
+ * * [UNDISPATCHED] uses [startCoroutineUndispatched].
+ * * [LAZY] does nothing.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>) =
+ when (this) {
+ CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion)
+ CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
+ CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
+ CoroutineStart.LAZY -> Unit // will start lazily
+ }
+
+ /**
+ * Returns `true` when [LAZY].
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ public val isLazy: Boolean get() = this === LAZY
+}
diff --git a/kotlinx-coroutines-core/common/src/Debug.common.kt b/kotlinx-coroutines-core/common/src/Debug.common.kt
new file mode 100644
index 00000000..dd09a6a1
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Debug.common.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+internal expect val DEBUG: Boolean
+internal expect val Any.hexAddress: String
+internal expect val Any.classSimpleName: String
+internal expect fun assert(value: () -> Boolean)
diff --git a/kotlinx-coroutines-core/common/src/Deferred.kt b/kotlinx-coroutines-core/common/src/Deferred.kt
new file mode 100644
index 00000000..04152f90
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Deferred.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.selects.*
+
+/**
+ * Deferred value is a non-blocking cancellable future &mdash; it is a [Job] with a result.
+ *
+ * It is created with the [async][CoroutineScope.async] coroutine builder or via the constructor of [CompletableDeferred] class.
+ * It is in [active][isActive] state while the value is being computed.
+ *
+ * `Deferred` has the same state machine as the [Job] with additional convenience methods to retrieve
+ * the successful or failed result of the computation that was carried out. The result of the deferred is
+ * available when it is [completed][isCompleted] and can be retrieved by [await] method, which throws
+ * an exception if the deferred had failed.
+ * Note that a _cancelled_ deferred is also considered as completed.
+ * The corresponding exception can be retrieved via [getCompletionExceptionOrNull] from a completed instance of deferred.
+ *
+ * Usually, a deferred value is created in _active_ state (it is created and started).
+ * However, the [async][CoroutineScope.async] coroutine builder has an optional `start` parameter that creates a deferred value in _new_ state
+ * when this parameter is set to [CoroutineStart.LAZY].
+ * Such a deferred can be be made _active_ by invoking [start], [join], or [await].
+ *
+ * A deferred value is a [Job]. A job in the
+ * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/coroutine-context.html)
+ * of [async][CoroutineScope.async] builder represents the coroutine itself.
+ *
+ * All functions on this interface and on all interfaces derived from it are **thread-safe** and can
+ * be safely invoked from concurrent coroutines without external synchronization.
+ */
+public interface Deferred<out T> : Job {
+
+ /**
+ * Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete,
+ * returning the resulting value or throwing the corresponding exception if the deferred was cancelled.
+ *
+ * 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].
+ *
+ * This function can be used in [select] invocation with [onAwait] clause.
+ * Use [isCompleted] to check for completion of this deferred value without waiting.
+ */
+ public suspend fun await(): T
+
+ /**
+ * Clause for [select] expression of [await] suspending function that selects with the deferred value when it is
+ * resolved. The [select] invocation fails if the deferred value completes exceptionally (either fails or
+ * it cancelled).
+ */
+ public val onAwait: SelectClause1<T>
+
+ /**
+ * Returns *completed* result or throws [IllegalStateException] if this deferred value has not
+ * [completed][isCompleted] yet. It throws the corresponding exception if this deferred was [cancelled][isCancelled].
+ *
+ * This function is designed to be used from [invokeOnCompletion] handlers, when there is an absolute certainty that
+ * the value is already complete. See also [getCompletionExceptionOrNull].
+ *
+ * **Note: This is an experimental api.** This function may be removed or renamed in the future.
+ */
+ @ExperimentalCoroutinesApi
+ public fun getCompleted(): T
+
+ /**
+ * Returns *completion exception* result if this deferred was [cancelled][isCancelled] and has [completed][isCompleted],
+ * `null` if it had completed normally, or throws [IllegalStateException] if this deferred value has not
+ * [completed][isCompleted] yet.
+ *
+ * This function is designed to be used from [invokeOnCompletion] handlers, when there is an absolute certainty that
+ * the value is already complete. See also [getCompleted].
+ *
+ * **Note: This is an experimental api.** This function may be removed or renamed in the future.
+ */
+ @ExperimentalCoroutinesApi
+ public fun getCompletionExceptionOrNull(): Throwable?
+}
diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt
new file mode 100644
index 00000000..acb92402
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Delay.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+
+/**
+ * This dispatcher _feature_ is implemented by [CoroutineDispatcher] implementations that natively support
+ * scheduled execution of tasks.
+ *
+ * Implementation of this interface affects operation of
+ * [delay][kotlinx.coroutines.delay] and [withTimeout] functions.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public interface Delay {
+ /**
+ * Delays coroutine for a given time without blocking a thread and resumes it after a specified time.
+ * 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].
+ */
+ suspend fun delay(time: Long) {
+ if (time <= 0) return // don't delay
+ return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, it) }
+ }
+
+ /**
+ * Schedules resume of a specified [continuation] after a specified delay [timeMillis].
+ *
+ * Continuation **must be scheduled** to resume even if it is already cancelled, because a cancellation is just
+ * an exception that the coroutine that used `delay` might wanted to catch and process. It might
+ * need to close some resources in its `finally` blocks, for example.
+ *
+ * This implementation is supposed to use dispatcher's native ability for scheduled execution in its thread(s).
+ * In order to avoid an extra delay of execution, the following code shall be used to resume this
+ * [continuation] when the code is already executing in the appropriate thread:
+ *
+ * ```kotlin
+ * with(continuation) { resumeUndispatchedWith(Unit) }
+ * ```
+ */
+ fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>)
+
+ /**
+ * Schedules invocation of a specified [block] after a specified delay [timeMillis].
+ * The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] of this invocation
+ * request if it is not needed anymore.
+ *
+ * This implementation uses a built-in single-threaded scheduled executor service.
+ */
+ fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle =
+ DefaultDelay.invokeOnTimeout(timeMillis, block)
+}
+
+/**
+ * Delays coroutine for a given time without blocking a thread and resumes it after a specified time.
+ * 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].
+ *
+ * Note that delay can be used in [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
+ *
+ * Implementation note: how exactly time is tracked is an implementation detail of [CoroutineDispatcher] in the context.
+ * @param timeMillis time in milliseconds.
+ */
+public suspend fun delay(timeMillis: Long) {
+ if (timeMillis <= 0) return // don't delay
+ return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
+ cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
+ }
+}
+
+/** Returns [Delay] implementation of the given context */
+internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay
diff --git a/kotlinx-coroutines-core/common/src/Dispatched.kt b/kotlinx-coroutines-core/common/src/Dispatched.kt
new file mode 100644
index 00000000..a9624bd8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Dispatched.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2016-2018 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.jvm.*
+
+@Suppress("PrivatePropertyName")
+@SharedImmutable
+private val UNDEFINED = Symbol("UNDEFINED")
+
+/**
+ * Executes given [block] as part of current event loop, updating current continuation
+ * mode and state if continuation is not resumed immediately.
+ * [doYield] indicates whether current continuation is yielding (to provide fast-path if event-loop is empty).
+ * Returns `true` if execution of continuation was queued (trampolined) or `false` otherwise.
+ */
+private inline fun DispatchedContinuation<*>.executeUnconfined(
+ contState: Any?, mode: Int, doYield: Boolean = false,
+ block: () -> Unit
+) : Boolean {
+ val eventLoop = ThreadLocalEventLoop.eventLoop
+ // If we are yielding and unconfined queue is empty, we can bail out as part of fast path
+ if (doYield && eventLoop.isUnconfinedQueueEmpty) return false
+ return if (eventLoop.isUnconfinedLoopActive) {
+ // When unconfined loop is active -- dispatch continuation for execution to avoid stack overflow
+ _state = contState
+ resumeMode = mode
+ eventLoop.dispatchUnconfined(this)
+ true // queued into the active loop
+ } else {
+ // Was not active -- run event loop until all unconfined tasks are executed
+ runUnconfinedEventLoop(eventLoop, block = block)
+ false
+ }
+}
+
+private fun DispatchedTask<*>.resumeUnconfined() {
+ val eventLoop = ThreadLocalEventLoop.eventLoop
+ if (eventLoop.isUnconfinedLoopActive) {
+ // When unconfined loop is active -- dispatch continuation for execution to avoid stack overflow
+ eventLoop.dispatchUnconfined(this)
+ } else {
+ // Was not active -- run event loop until all unconfined tasks are executed
+ runUnconfinedEventLoop(eventLoop) {
+ resume(delegate, MODE_UNDISPATCHED)
+ }
+ }
+}
+
+private inline fun DispatchedTask<*>.runUnconfinedEventLoop(
+ eventLoop: EventLoop,
+ block: () -> Unit
+) {
+ eventLoop.incrementUseCount(unconfined = true)
+ try {
+ block()
+ while (true) {
+ // break when all unconfined continuations where executed
+ if (!eventLoop.processUnconfinedEvent()) break
+ }
+ } catch (e: Throwable) {
+ /*
+ * This exception doesn't happen normally, only if we have a bug in implementation.
+ * Report it as a fatal exception.
+ */
+ handleFatalException(e, null)
+ } finally {
+ eventLoop.decrementUseCount(unconfined = true)
+ }
+}
+
+internal class DispatchedContinuation<in T>(
+ @JvmField val dispatcher: CoroutineDispatcher,
+ @JvmField val continuation: Continuation<T>
+) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), CoroutineStackFrame, Continuation<T> by continuation {
+ @JvmField
+ @Suppress("PropertyName")
+ internal var _state: Any? = UNDEFINED
+ override val callerFrame: CoroutineStackFrame? = continuation as? CoroutineStackFrame
+ override fun getStackTraceElement(): StackTraceElement? = null
+ @JvmField // pre-cached value to avoid ctx.fold on every resumption
+ internal val countOrElement = threadContextElements(context)
+
+ override fun takeState(): Any? {
+ val state = _state
+ assert { state !== UNDEFINED } // fail-fast if repeatedly invoked
+ _state = UNDEFINED
+ return state
+ }
+
+ override val delegate: Continuation<T>
+ get() = this
+
+ override fun resumeWith(result: Result<T>) {
+ val context = continuation.context
+ val state = result.toState()
+ if (dispatcher.isDispatchNeeded(context)) {
+ _state = state
+ resumeMode = MODE_ATOMIC_DEFAULT
+ dispatcher.dispatch(context, this)
+ } else {
+ executeUnconfined(state, MODE_ATOMIC_DEFAULT) {
+ withCoroutineContext(this.context, countOrElement) {
+ continuation.resumeWith(result)
+ }
+ }
+ }
+ }
+
+ @Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
+ inline fun resumeCancellable(value: T) {
+ if (dispatcher.isDispatchNeeded(context)) {
+ _state = value
+ resumeMode = MODE_CANCELLABLE
+ dispatcher.dispatch(context, this)
+ } else {
+ executeUnconfined(value, MODE_CANCELLABLE) {
+ if (!resumeCancelled()) {
+ resumeUndispatched(value)
+ }
+ }
+ }
+ }
+
+ @Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
+ inline fun resumeCancellableWithException(exception: Throwable) {
+ val context = continuation.context
+ val state = CompletedExceptionally(exception)
+ if (dispatcher.isDispatchNeeded(context)) {
+ _state = CompletedExceptionally(exception)
+ resumeMode = MODE_CANCELLABLE
+ dispatcher.dispatch(context, this)
+ } else {
+ executeUnconfined(state, MODE_CANCELLABLE) {
+ if (!resumeCancelled()) {
+ resumeUndispatchedWithException(exception)
+ }
+ }
+ }
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun resumeCancelled(): Boolean {
+ val job = context[Job]
+ if (job != null && !job.isActive) {
+ resumeWithException(job.getCancellationException())
+ return true
+ }
+
+ return false
+ }
+
+ @Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
+ inline fun resumeUndispatched(value: T) {
+ withCoroutineContext(context, countOrElement) {
+ continuation.resume(value)
+ }
+ }
+
+ @Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
+ inline fun resumeUndispatchedWithException(exception: Throwable) {
+ withCoroutineContext(context, countOrElement) {
+ continuation.resumeWithStackTrace(exception)
+ }
+ }
+
+ // used by "yield" implementation
+ internal fun dispatchYield(value: T) {
+ val context = continuation.context
+ _state = value
+ resumeMode = MODE_CANCELLABLE
+ dispatcher.dispatchYield(context, this)
+ }
+
+ override fun toString(): String =
+ "DispatchedContinuation[$dispatcher, ${continuation.toDebugString()}]"
+}
+
+internal fun <T> Continuation<T>.resumeCancellable(value: T) = when (this) {
+ is DispatchedContinuation -> resumeCancellable(value)
+ else -> resume(value)
+}
+
+internal fun <T> Continuation<T>.resumeCancellableWithException(exception: Throwable) = when (this) {
+ is DispatchedContinuation -> resumeCancellableWithException(exception)
+ else -> resumeWithStackTrace(exception)
+}
+
+internal fun <T> Continuation<T>.resumeDirect(value: T) = when (this) {
+ is DispatchedContinuation -> continuation.resume(value)
+ else -> resume(value)
+}
+
+internal fun <T> Continuation<T>.resumeDirectWithException(exception: Throwable) = when (this) {
+ is DispatchedContinuation -> continuation.resumeWithStackTrace(exception)
+ else -> resumeWithStackTrace(exception)
+}
+
+internal abstract class DispatchedTask<in T>(
+ @JvmField public var resumeMode: Int
+) : SchedulerTask() {
+ internal abstract val delegate: Continuation<T>
+
+ internal abstract fun takeState(): Any?
+
+ internal open fun cancelResult(state: Any?, cause: Throwable) {}
+
+ @Suppress("UNCHECKED_CAST")
+ internal open fun <T> getSuccessfulResult(state: Any?): T =
+ state as T
+
+ internal fun getExceptionalResult(state: Any?): Throwable? =
+ (state as? CompletedExceptionally)?.cause
+
+ public final override fun run() {
+ val taskContext = this.taskContext
+ var fatalException: Throwable? = null
+ 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) {
+ val exception = getExceptionalResult(state)
+ val job = if (resumeMode.isCancellableMode) context[Job] else null
+ /*
+ * Check whether continuation was originally resumed with an exception.
+ * If so, it dominates cancellation, otherwise the original exception
+ * will be silently lost.
+ */
+ if (exception == null && job != null && !job.isActive) {
+ val cause = job.getCancellationException()
+ cancelResult(state, cause)
+ continuation.resumeWithStackTrace(cause)
+ } else {
+ if (exception != null) continuation.resumeWithStackTrace(exception)
+ else continuation.resume(getSuccessfulResult(state))
+ }
+ }
+ } catch (e: Throwable) {
+ // This instead of runCatching to have nicer stacktrace and debug experience
+ fatalException = e
+ } finally {
+ val result = runCatching { taskContext.afterTask() }
+ handleFatalException(fatalException, result.exceptionOrNull())
+ }
+ }
+
+ /**
+ * Machinery that handles fatal exceptions in kotlinx.coroutines.
+ * There are two kinds of fatal exceptions:
+ *
+ * 1) Exceptions from kotlinx.coroutines code. Such exceptions indicate that either
+ * the library or the compiler has a bug that breaks internal invariants.
+ * They usually have specific workarounds, but require careful study of the cause and should
+ * be reported to the maintainers and fixed on the library's side anyway.
+ *
+ * 2) Exceptions from [ThreadContextElement.updateThreadContext] and [ThreadContextElement.restoreThreadContext].
+ * While a user code can trigger such exception by providing an improper implementation of [ThreadContextElement],
+ * we can't ignore it because it may leave coroutine in the inconsistent state.
+ * If you encounter such exception, you can either disable this context element or wrap it into
+ * another context element that catches all exceptions and handles it in the application specific manner.
+ *
+ * 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?) {
+ if (exception === null && finallyException === null) return
+ if (exception !== null && finallyException !== null) {
+ exception.addSuppressedThrowable(finallyException)
+ }
+
+ val cause = exception ?: finallyException
+ val reason = CoroutinesInternalError("Fatal exception in coroutines machinery for $this. " +
+ "Please read KDoc to 'handleFatalException' method and report this incident to maintainers", cause!!)
+ handleCoroutineException(this.delegate.context, reason)
+ }
+}
+
+internal fun DispatchedContinuation<Unit>.yieldUndispatched(): Boolean =
+ executeUnconfined(Unit, MODE_CANCELLABLE, doYield = true) {
+ run()
+ }
+
+internal fun <T> DispatchedTask<T>.dispatch(mode: Int = MODE_CANCELLABLE) {
+ val delegate = this.delegate
+ if (mode.isDispatchedMode && delegate is DispatchedContinuation<*> && mode.isCancellableMode == resumeMode.isCancellableMode) {
+ // dispatch directly using this instance's Runnable implementation
+ val dispatcher = delegate.dispatcher
+ val context = delegate.context
+ if (dispatcher.isDispatchNeeded(context)) {
+ dispatcher.dispatch(context, this)
+ } else {
+ resumeUnconfined()
+ }
+ } else {
+ resume(delegate, mode)
+ }
+}
+
+internal fun <T> DispatchedTask<T>.resume(delegate: Continuation<T>, useMode: Int) {
+ // slow-path - use delegate
+ val state = takeState()
+ val exception = getExceptionalResult(state)
+ if (exception != null) {
+ /*
+ * Recover stacktrace for non-dispatched tasks.
+ * We usually do not recover stacktrace in a `resume` as all resumes go through `DispatchedTask.run`
+ * and we recover stacktraces there, but this is not the case for a `suspend fun main()` that knows nothing about
+ * kotlinx.coroutines and DispatchedTask
+ */
+ val recovered = if (delegate is DispatchedTask<*>) exception else recoverStackTrace(exception, delegate)
+ delegate.resumeWithExceptionMode(recovered, useMode)
+ } else {
+ delegate.resumeMode(getSuccessfulResult(state), useMode)
+ }
+}
+
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Continuation<*>.resumeWithStackTrace(exception: Throwable) {
+ resumeWith(Result.failure(recoverStackTrace(exception, this)))
+}
diff --git a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
new file mode 100644
index 00000000..b3405683
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+/**
+ * Groups various implementations of [CoroutineDispatcher].
+ */
+public expect object Dispatchers {
+ /**
+ * The default [CoroutineDispatcher] that is used by all standard builders like
+ * [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc
+ * if neither a dispatcher nor any other [ContinuationInterceptor] is specified in their context.
+ *
+ * It is backed by a shared pool of threads on JVM. By default, the maximum number of threads used
+ * by this dispatcher is equal to the number of CPU cores, but is at least two.
+ */
+ public val Default: CoroutineDispatcher
+
+ /**
+ * A coroutine dispatcher that is confined to the Main thread operating with UI objects.
+ * Usually such dispatchers are single-threaded.
+ *
+ * Access to this property may throw an [IllegalStateException] if no main dispatchers are present in the classpath.
+ *
+ * Depending on platform and classpath it can be mapped to different dispatchers:
+ * - On JS and Native it is equivalent to the [Default] dispatcher.
+ * - On JVM it either the Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by the
+ * [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
+ *
+ * In order to work with the `Main` dispatcher, the following artifact should be added to the project runtime dependencies:
+ * - `kotlinx-coroutines-android` &mdash; for Android Main thread dispatcher
+ * - `kotlinx-coroutines-javafx` &mdash; for JavaFx Application thread dispatcher
+ * - `kotlinx-coroutines-swing` &mdash; for Swing EDT dispatcher
+ *
+ * Implementation note: [MainCoroutineDispatcher.immediate] is not supported on the Native and JS platforms.
+ */
+ public val Main: MainCoroutineDispatcher
+
+ /**
+ * A coroutine dispatcher that is not confined to any specific thread.
+ * It executes the initial continuation of a coroutine in the current call-frame
+ * and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without
+ * mandating any specific threading policy. Nested coroutines launched in this dispatcher form an event-loop to avoid
+ * stack overflows.
+ *
+ * ### Event loop
+ * Event loop semantics is a purely internal concept and have no guarantees on the order of execution
+ * except that all queued coroutines will be executed on the current thread in the lexical scope of the outermost
+ * unconfined coroutine.
+ *
+ * For example, the following code:
+ * ```
+ * withContext(Dispatcher.Unconfined) {
+ * println(1)
+ * withContext(Dispatcher.Unconfined) { // Nested unconfined
+ * println(2)
+ * }
+ * println(3)
+ * }
+ * println("Done")
+ * ```
+ * Can print both "1 2 3" and "1 3 2", this is an implementation detail that can be changed.
+ * But it is guaranteed that "Done" will be printed only when both `withContext` calls are completed.
+ *
+ * Note that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption,
+ * but still want to execute it in the current call-frame until its first suspension, then you can use
+ * an optional [CoroutineStart] parameter in coroutine builders like
+ * [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to
+ * the value of [CoroutineStart.UNDISPATCHED].
+ */
+ public val Unconfined: CoroutineDispatcher
+}
diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
new file mode 100644
index 00000000..71070598
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2016-2019 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.internal.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Extended by [CoroutineDispatcher] implementations that have event loop inside and can
+ * be asked to process next event from their event queue.
+ *
+ * It may optionally implement [Delay] interface and support time-scheduled tasks.
+ * It is created or pigged back onto (see [ThreadLocalEventLoop])
+ * by `runBlocking` and by [Dispatchers.Unconfined].
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+internal abstract class EventLoop : CoroutineDispatcher() {
+ /**
+ * Counts the number of nested `runBlocking` and [Dispatchers.Unconfined] that use this event loop.
+ */
+ private var useCount = 0L
+
+ /**
+ * Set to true on any use by `runBlocking`, because it potentially leaks this loop to other threads, so
+ * this instance must be properly shutdown. We don't need to shutdown event loop that was used solely
+ * by [Dispatchers.Unconfined] -- it can be left as [ThreadLocalEventLoop] and reused next time.
+ */
+ private var shared = false
+
+ /**
+ * Queue used by [Dispatchers.Unconfined] tasks.
+ * These tasks are thread-local for performance and take precedence over the rest of the queue.
+ */
+ private var unconfinedQueue: ArrayQueue<DispatchedTask<*>>? = null
+
+ /**
+ * Processes next event in this event loop.
+ *
+ * The result of this function is to be interpreted like this:
+ * * `<= 0` -- there are potentially more events for immediate processing;
+ * * `> 0` -- a number of nanoseconds to wait for next scheduled event;
+ * * [Long.MAX_VALUE] -- no more events.
+ *
+ * **NOTE**: Must be invoked only from the event loop's thread
+ * (no check for performance reasons, may be added in the future).
+ */
+ public open fun processNextEvent(): Long {
+ if (!processUnconfinedEvent()) return Long.MAX_VALUE
+ return nextTime
+ }
+
+ protected open val isEmpty: Boolean get() = isUnconfinedQueueEmpty
+
+ protected open val nextTime: Long
+ get() {
+ val queue = unconfinedQueue ?: return Long.MAX_VALUE
+ return if (queue.isEmpty) Long.MAX_VALUE else 0L
+ }
+
+ public fun processUnconfinedEvent(): Boolean {
+ val queue = unconfinedQueue ?: return false
+ val task = queue.removeFirstOrNull() ?: return false
+ task.run()
+ return true
+ }
+ /**
+ * Returns `true` if the invoking `runBlocking(context) { ... }` that was passed this event loop in its context
+ * parameter should call [processNextEvent] for this event loop (otherwise, it will process thread-local one).
+ * By default, event loop implementation is thread-local and should not processed in the context
+ * (current thread's event loop should be processed instead).
+ */
+ public open fun shouldBeProcessedFromContext(): Boolean = false
+
+ /**
+ * Dispatches task whose dispatcher returned `false` from [CoroutineDispatcher.isDispatchNeeded]
+ * into the current event loop.
+ */
+ public fun dispatchUnconfined(task: DispatchedTask<*>) {
+ val queue = unconfinedQueue ?:
+ ArrayQueue<DispatchedTask<*>>().also { unconfinedQueue = it }
+ queue.addLast(task)
+ }
+
+ public val isActive: Boolean
+ get() = useCount > 0
+
+ public val isUnconfinedLoopActive: Boolean
+ get() = useCount >= delta(unconfined = true)
+
+ // May only be used from the event loop's thread
+ public val isUnconfinedQueueEmpty: Boolean
+ get() = unconfinedQueue?.isEmpty ?: true
+
+ private fun delta(unconfined: Boolean) =
+ if (unconfined) (1L shl 32) else 1L
+
+ fun incrementUseCount(unconfined: Boolean = false) {
+ useCount += delta(unconfined)
+ if (!unconfined) shared = true
+ }
+
+ fun decrementUseCount(unconfined: Boolean = false) {
+ useCount -= delta(unconfined)
+ if (useCount > 0) return
+ assert { useCount == 0L } // "Extra decrementUseCount"
+ if (shared) {
+ // shut it down and remove from ThreadLocalEventLoop
+ shutdown()
+ }
+ }
+
+ protected open fun shutdown() {}
+}
+
+@NativeThreadLocal
+internal object ThreadLocalEventLoop {
+ private val ref = CommonThreadLocal<EventLoop?>()
+
+ internal val eventLoop: EventLoop
+ get() = ref.get() ?: createEventLoop().also { ref.set(it) }
+
+ internal fun currentOrNull(): EventLoop? =
+ ref.get()
+
+ internal fun resetEventLoop() {
+ ref.set(null)
+ }
+
+ internal fun setEventLoop(eventLoop: EventLoop) {
+ ref.set(eventLoop)
+ }
+}
+
+@SharedImmutable
+private val DISPOSED_TASK = Symbol("REMOVED_TASK")
+
+// results for scheduleImpl
+private const val SCHEDULE_OK = 0
+private const val SCHEDULE_COMPLETED = 1
+private const val SCHEDULE_DISPOSED = 2
+
+private const val MS_TO_NS = 1_000_000L
+private const val MAX_MS = Long.MAX_VALUE / MS_TO_NS
+
+/**
+ * First-line overflow protection -- limit maximal delay.
+ * Delays longer than this one (~146 years) are considered to be delayed "forever".
+ */
+private const val MAX_DELAY_NS = Long.MAX_VALUE / 2
+
+internal fun delayToNanos(timeMillis: Long): Long = when {
+ timeMillis <= 0 -> 0L
+ timeMillis >= MAX_MS -> Long.MAX_VALUE
+ else -> timeMillis * MS_TO_NS
+}
+
+internal fun delayNanosToMillis(timeNanos: Long): Long =
+ timeNanos / MS_TO_NS
+
+@SharedImmutable
+private val CLOSED_EMPTY = Symbol("CLOSED_EMPTY")
+
+private typealias Queue<T> = LockFreeTaskQueueCore<T>
+
+internal expect abstract class EventLoopImplPlatform() : EventLoop {
+ // Called to unpark this event loop's thread
+ protected fun unpark()
+
+ // Called to reschedule to DefaultExecutor when this event loop is complete
+ protected fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask)
+}
+
+internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
+ // null | CLOSED_EMPTY | task | Queue<Runnable>
+ private val _queue = atomic<Any?>(null)
+
+ // Allocated only only once
+ private val _delayed = atomic<DelayedTaskQueue?>(null)
+
+ @Volatile
+ private var isCompleted = false
+
+ override val isEmpty: Boolean get() {
+ if (!isUnconfinedQueueEmpty) return false
+ val delayed = _delayed.value
+ if (delayed != null && !delayed.isEmpty) return false
+ val queue = _queue.value
+ return when (queue) {
+ null -> true
+ is Queue<*> -> queue.isEmpty
+ else -> queue === CLOSED_EMPTY
+ }
+ }
+
+ protected override val nextTime: Long
+ get() {
+ if (super.nextTime == 0L) return 0L
+ val queue = _queue.value
+ when {
+ queue === null -> {} // empty queue -- proceed
+ queue is Queue<*> -> if (!queue.isEmpty) return 0 // non-empty queue
+ queue === CLOSED_EMPTY -> return Long.MAX_VALUE // no more events -- closed
+ else -> return 0 // non-empty queue
+ }
+ val nextDelayedTask = _delayed.value?.peek() ?: return Long.MAX_VALUE
+ return (nextDelayedTask.nanoTime - nanoTime()).coerceAtLeast(0)
+ }
+
+ override fun shutdown() {
+ // Clean up thread-local reference here -- this event loop is shutting down
+ ThreadLocalEventLoop.resetEventLoop()
+ // We should signal that this event loop should not accept any more tasks
+ // and process queued events (that could have been added after last processNextEvent)
+ isCompleted = true
+ closeQueue()
+ // complete processing of all queued tasks
+ while (processNextEvent() <= 0) { /* spin */ }
+ // reschedule the rest of delayed tasks
+ rescheduleAllDelayed()
+ }
+
+ public override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ val timeNanos = delayToNanos(timeMillis)
+ if (timeNanos < MAX_DELAY_NS) {
+ val now = nanoTime()
+ DelayedResumeTask(now + timeNanos, continuation).also { task ->
+ continuation.disposeOnCancellation(task)
+ schedule(now, task)
+ }
+ }
+ }
+
+ protected fun scheduleInvokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val timeNanos = delayToNanos(timeMillis)
+ return if (timeNanos < MAX_DELAY_NS) {
+ val now = nanoTime()
+ DelayedRunnableTask(now + timeNanos, block).also { task ->
+ schedule(now, task)
+ }
+ } else {
+ NonDisposableHandle
+ }
+ }
+
+ override fun processNextEvent(): Long {
+ // unconfined events take priority
+ if (processUnconfinedEvent()) return nextTime
+ // queue all delayed tasks that are due to be executed
+ val delayed = _delayed.value
+ if (delayed != null && !delayed.isEmpty) {
+ val now = nanoTime()
+ while (true) {
+ // make sure that moving from delayed to queue removes from delayed only after it is added to queue
+ // to make sure that 'isEmpty' and `nextTime` that check both of them
+ // do not transiently report that both delayed and queue are empty during move
+ delayed.removeFirstIf {
+ if (it.timeToExecute(now)) {
+ enqueueImpl(it)
+ } else
+ false
+ } ?: break // quit loop when nothing more to remove or enqueueImpl returns false on "isComplete"
+ }
+ }
+ // then process one event from queue
+ dequeue()?.run()
+ return nextTime
+ }
+
+ public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block)
+
+ public fun enqueue(task: Runnable) {
+ if (enqueueImpl(task)) {
+ // todo: we should unpark only when this delayed task became first in the queue
+ unpark()
+ } else {
+ DefaultExecutor.enqueue(task)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun enqueueImpl(task: Runnable): Boolean {
+ _queue.loop { queue ->
+ if (isCompleted) return false // fail fast if already completed, may still add, but queues will close
+ when (queue) {
+ null -> if (_queue.compareAndSet(null, task)) return true
+ is Queue<*> -> {
+ when ((queue as Queue<Runnable>).addLast(task)) {
+ Queue.ADD_SUCCESS -> return true
+ Queue.ADD_CLOSED -> return false
+ Queue.ADD_FROZEN -> _queue.compareAndSet(queue, queue.next())
+ }
+ }
+ else -> when {
+ queue === CLOSED_EMPTY -> return false
+ else -> {
+ // update to full-blown queue to add one more
+ val newQueue = Queue<Runnable>(Queue.INITIAL_CAPACITY, singleConsumer = true)
+ newQueue.addLast(queue as Runnable)
+ newQueue.addLast(task)
+ if (_queue.compareAndSet(queue, newQueue)) return true
+ }
+ }
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun dequeue(): Runnable? {
+ _queue.loop { queue ->
+ when (queue) {
+ null -> return null
+ is Queue<*> -> {
+ val result = (queue as Queue<Runnable>).removeFirstOrNull()
+ if (result !== Queue.REMOVE_FROZEN) return result as Runnable?
+ _queue.compareAndSet(queue, queue.next())
+ }
+ else -> when {
+ queue === CLOSED_EMPTY -> return null
+ else -> if (_queue.compareAndSet(queue, null)) return queue as Runnable
+ }
+ }
+ }
+ }
+
+ private fun closeQueue() {
+ assert { isCompleted }
+ _queue.loop { queue ->
+ when (queue) {
+ null -> if (_queue.compareAndSet(null, CLOSED_EMPTY)) return
+ is Queue<*> -> {
+ queue.close()
+ return
+ }
+ else -> when {
+ queue === CLOSED_EMPTY -> return
+ else -> {
+ // update to full-blown queue to close
+ val newQueue = Queue<Runnable>(Queue.INITIAL_CAPACITY, singleConsumer = true)
+ newQueue.addLast(queue as Runnable)
+ if (_queue.compareAndSet(queue, newQueue)) return
+ }
+ }
+ }
+ }
+
+ }
+
+ public fun schedule(now: Long, delayedTask: DelayedTask) {
+ when (scheduleImpl(now, delayedTask)) {
+ SCHEDULE_OK -> if (shouldUnpark(delayedTask)) unpark()
+ SCHEDULE_COMPLETED -> reschedule(now, delayedTask)
+ SCHEDULE_DISPOSED -> {} // do nothing -- task was already disposed
+ else -> error("unexpected result")
+ }
+ }
+
+ private fun shouldUnpark(task: DelayedTask): Boolean = _delayed.value?.peek() === task
+
+ private fun scheduleImpl(now: Long, delayedTask: DelayedTask): Int {
+ if (isCompleted) return SCHEDULE_COMPLETED
+ val delayedQueue = _delayed.value ?: run {
+ _delayed.compareAndSet(null, DelayedTaskQueue(now))
+ _delayed.value!!
+ }
+ return delayedTask.scheduleTask(now, delayedQueue, this)
+ }
+
+ // It performs "hard" shutdown for test cleanup purposes
+ protected fun resetAll() {
+ _queue.value = null
+ _delayed.value = null
+ }
+
+ // This is a "soft" (normal) shutdown
+ private fun rescheduleAllDelayed() {
+ val now = nanoTime()
+ while (true) {
+ /*
+ * `removeFirstOrNull` below is the only operation on DelayedTask & ThreadSafeHeap that is not
+ * synchronized on DelayedTask itself. All other operation are synchronized both on
+ * DelayedTask & ThreadSafeHeap instances (in this order). It is still safe, because `dispose`
+ * first removes DelayedTask from the heap (under synchronization) then
+ * assign "_heap = DISPOSED_TASK", so there cannot be ever a race to _heap reference update.
+ */
+ val delayedTask = _delayed.value?.removeFirstOrNull() ?: break
+ reschedule(now, delayedTask)
+ }
+ }
+
+ internal abstract class DelayedTask(
+ /**
+ * This field can be only modified in [scheduleTask] before putting this DelayedTask
+ * into heap to avoid overflow and corruption of heap data structure.
+ */
+ @JvmField var nanoTime: Long
+ ) : Runnable, Comparable<DelayedTask>, DisposableHandle, ThreadSafeHeapNode {
+ private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK
+
+ override var heap: ThreadSafeHeap<*>?
+ get() = _heap as? ThreadSafeHeap<*>
+ set(value) {
+ require(_heap !== DISPOSED_TASK) // this can never happen, it is always checked before adding/removing
+ _heap = value
+ }
+
+ override var index: Int = -1
+
+ override fun compareTo(other: DelayedTask): Int {
+ val dTime = nanoTime - other.nanoTime
+ return when {
+ dTime > 0 -> 1
+ dTime < 0 -> -1
+ else -> 0
+ }
+ }
+
+ fun timeToExecute(now: Long): Boolean = now - nanoTime >= 0L
+
+ @Synchronized
+ fun scheduleTask(now: Long, delayed: DelayedTaskQueue, eventLoop: EventLoopImplBase): Int {
+ if (_heap === DISPOSED_TASK) return SCHEDULE_DISPOSED // don't add -- was already disposed
+ delayed.addLastIf(this) { firstTask ->
+ if (eventLoop.isCompleted) return SCHEDULE_COMPLETED // non-local return from scheduleTask
+ /**
+ * We are about to add new task and we have to make sure that [DelayedTaskQueue]
+ * invariant is maintained. The code in this lambda is additionally executed under
+ * the lock of [DelayedTaskQueue] and working with [DelayedTaskQueue.timeNow] here is thread-safe.
+ */
+ if (firstTask == null) {
+ /**
+ * When adding the first delayed task we simply update queue's [DelayedTaskQueue.timeNow] to
+ * the current now time even if that means "going backwards in time". This makes the structure
+ * self-correcting in spite of wild jumps in `nanoTime()` measurements once all delayed tasks
+ * are removed from the delayed queue for execution.
+ */
+ delayed.timeNow = now
+ } else {
+ /**
+ * Carefully update [DelayedTaskQueue.timeNow] so that it does not sweep past first's tasks time
+ * and only goes forward in time. We cannot let it go backwards in time or invariant can be
+ * violated for tasks that were already scheduled.
+ */
+ val firstTime = firstTask.nanoTime
+ // compute min(now, firstTime) using a wrap-safe check
+ val minTime = if (firstTime - now >= 0) now else firstTime
+ // update timeNow only when going forward in time
+ if (minTime - delayed.timeNow > 0) delayed.timeNow = minTime
+ }
+ /**
+ * Here [DelayedTaskQueue.timeNow] was already modified and we have to double-check that newly added
+ * task does not violate [DelayedTaskQueue] invariant because of that. Note also that this scheduleTask
+ * function can be called to reschedule from one queue to another and this might be another reason
+ * where new task's time might now violate invariant.
+ * We correct invariant violation (if any) by simply changing this task's time to now.
+ */
+ if (nanoTime - delayed.timeNow < 0) nanoTime = delayed.timeNow
+ true
+ }
+ return SCHEDULE_OK
+ }
+
+ @Synchronized
+ final override fun dispose() {
+ val heap = _heap
+ if (heap === DISPOSED_TASK) return // already disposed
+ @Suppress("UNCHECKED_CAST")
+ (heap as? DelayedTaskQueue)?.remove(this) // remove if it is in heap (first)
+ _heap = DISPOSED_TASK // never add again to any heap
+ }
+
+ override fun toString(): String = "Delayed[nanos=$nanoTime]"
+ }
+
+ private inner class DelayedResumeTask(
+ nanoTime: Long,
+ private val cont: CancellableContinuation<Unit>
+ ) : DelayedTask(nanoTime) {
+ override fun run() { with(cont) { resumeUndispatched(Unit) } }
+ override fun toString(): String = super.toString() + cont.toString()
+ }
+
+ private class DelayedRunnableTask(
+ nanoTime: Long,
+ private val block: Runnable
+ ) : DelayedTask(nanoTime) {
+ override fun run() { block.run() }
+ override fun toString(): String = super.toString() + block.toString()
+ }
+
+ /**
+ * Delayed task queue maintains stable time-comparision invariant despite potential wraparounds in
+ * long nano time measurements by maintaining last observed [timeNow]. It protects the integrity of the
+ * heap data structure in spite of potential non-monotonicity of `nanoTime()` source.
+ * The invariant is that for every scheduled [DelayedTask]:
+ *
+ * ```
+ * delayedTask.nanoTime - timeNow >= 0
+ * ```
+ *
+ * So the comparison of scheduled tasks via [DelayedTask.compareTo] is always stable as
+ * scheduled [DelayedTask.nanoTime] can be at most [Long.MAX_VALUE] apart. This invariant is maintained when
+ * new tasks are added by [DelayedTask.scheduleTask] function and it cannot be violated when tasks are removed
+ * (so there is nothing special to do there).
+ */
+ internal class DelayedTaskQueue(
+ @JvmField var timeNow: Long
+ ) : ThreadSafeHeap<DelayedTask>()
+}
+
+internal expect fun createEventLoop(): EventLoop
+
+internal expect fun nanoTime(): Long
+
+internal expect object DefaultExecutor {
+ public fun enqueue(task: Runnable)
+}
+
diff --git a/kotlinx-coroutines-core/common/src/Exceptions.common.kt b/kotlinx-coroutines-core/common/src/Exceptions.common.kt
new file mode 100644
index 00000000..62b6ba4d
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Exceptions.common.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public expect class CompletionHandlerException(message: String, cause: Throwable) : RuntimeException
+
+public expect open class CancellationException(message: String?) : IllegalStateException
+
+@Suppress("FunctionName")
+public expect fun CancellationException(message: String?, cause: Throwable?) : CancellationException
+
+internal expect class JobCancellationException(
+ message: String,
+ cause: Throwable?,
+ job: Job
+) : CancellationException {
+ internal val job: Job
+}
+
+internal class CoroutinesInternalError(message: String, cause: Throwable) : Error(message, cause)
+
+internal expect fun Throwable.addSuppressedThrowable(other: Throwable)
+// For use in tests
+internal expect val RECOVER_STACK_TRACES: Boolean \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
new file mode 100644
index 00000000..c6716bc9
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -0,0 +1,639 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("JobKt")
+@file:Suppress("DEPRECATION_ERROR", "RedundantUnitReturnType")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+// --------------- core job interfaces ---------------
+
+/**
+ * A background job. Conceptually, a job is a cancellable thing with a life-cycle that
+ * culminates in its completion.
+ *
+ * Jobs can be arranged into parent-child hierarchies where cancellation
+ * of a parent leads to immediate cancellation of all its [children]. Failure or cancellation of a child
+ * with an exception other than [CancellationException] immediately cancels its parent. This way, a parent
+ * can [cancel] its own children (including all their children recursively) without cancelling itself.
+ *
+ * The most basic instances of [Job] are created with [launch][CoroutineScope.launch] coroutine builder or with a
+ * `Job()` factory function. By default, a failure of any of the job's children leads to an immediate failure
+ * of its parent and cancellation of the rest of its children. This behavior can be customized using [SupervisorJob].
+ *
+ * Conceptually, an execution of the job does not produce a result value. Jobs are launched solely for their
+ * side-effects. See [Deferred] interface for a job that produces a result.
+ *
+ * A job has the following states:
+ *
+ * | **State** | [isActive] | [isCompleted] | [isCancelled] |
+ * | -------------------------------- | ---------- | ------------- | ------------- |
+ * | _New_ (optional initial state) | `false` | `false` | `false` |
+ * | _Active_ (default initial state) | `true` | `false` | `false` |
+ * | _Completing_ (transient state) | `true` | `false` | `false` |
+ * | _Cancelling_ (transient state) | `false` | `false` | `true` |
+ * | _Cancelled_ (final state) | `false` | `true` | `true` |
+ * | _Completed_ (final state) | `false` | `true` | `false` |
+ *
+ * Usually, a job is created in _active_ state (it is created and started). However, coroutine builders
+ * that provide an optional `start` parameter create a coroutine in _new_ state when this parameter is set to
+ * [CoroutineStart.LAZY]. Such a job can be made _active_ by invoking [start] or [join].
+ *
+ * A job is _active_ while the coroutine is working. Failure of the job with exception makes it _cancelling_.
+ * A job can be cancelled at any time with [cancel] function that forces it to transition to
+ * _cancelling_ state immediately. The job becomes _cancelled_ when it finishes executing its work.
+ *
+ * ```
+ * wait children
+ * +-----+ start +--------+ complete +-------------+ finish +-----------+
+ * | New | -----> | Active | ---------> | Completing | -------> | Completed |
+ * +-----+ +--------+ +-------------+ +-----------+
+ * | cancel / fail |
+ * | +----------------+
+ * | |
+ * V V
+ * +------------+ finish +-----------+
+ * | Cancelling | --------------------------------> | Cancelled |
+ * +------------+ +-----------+
+ * ```
+ *
+ * A `Job` instance in the
+ * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/coroutine-context.html)
+ * represents the coroutine itself.
+ *
+ * A job can have a _parent_ job. A job with a parent is cancelled when its parent is cancelled.
+ * Parent job waits in _completing_ or _cancelling_ state for all its children to complete before finishing.
+ * Note that _completing_ state is purely internal to the job. For an outside observer a _completing_ job is still
+ * active, while internally it is waiting for its children.
+ *
+ * Normal cancellation of a job is distinguished from its failure by the type of its cancellation exception cause.
+ * If the cause of cancellation is [CancellationException], then the job is considered to be _cancelled normally_.
+ * This usually happens when [cancel] is invoked without additional parameters. If the cause of cancellation is
+ * a different exception, then the job is considered to have _failed_. This usually happens when the code of the job
+ * encounters some problem and throws an exception.
+ *
+ * All functions on this interface and on all interfaces derived from it are **thread-safe** and can
+ * be safely invoked from concurrent coroutines without external synchronization.
+ */
+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
+ }
+ }
+
+ // ------------ state query ------------
+
+ /**
+ * Returns `true` when this job is active -- it was already started and has not completed nor was cancelled yet.
+ * The job that is waiting for its [children] to complete is still considered to be active if it
+ * was not cancelled nor failed.
+ *
+ * See [Job] documentation for more details on job states.
+ */
+ public val isActive: Boolean
+
+ /**
+ * Returns `true` when this job has completed for any reason. A job that was cancelled or failed
+ * and has finished its execution is also considered complete. Job becomes complete only after
+ * all its [children] complete.
+ *
+ * See [Job] documentation for more details on job states.
+ */
+ public val isCompleted: Boolean
+
+ /**
+ * Returns `true` if this job was cancelled for any reason, either by explicit invocation of [cancel] or
+ * because it had failed or its child or parent was cancelled.
+ * In the general case, it does not imply that the
+ * job has already [completed][isCompleted], because it may still be finishing whatever it was doing and
+ * waiting for its [children] to complete.
+ *
+ * See [Job] documentation for more details on cancellation and failures.
+ */
+ public val isCancelled: Boolean
+
+ /**
+ * Returns [CancellationException] that signals the completion of this job. This function is
+ * used by [cancellable][suspendCancellableCoroutine] suspending functions. They throw exception
+ * returned by this function when they suspend in the context of this job and this job becomes _complete_.
+ *
+ * This function returns the original [cancel] cause of this job if that `cause` was an instance of
+ * [CancellationException]. Otherwise (if this job was cancelled with a cause of a different type, or
+ * was cancelled without a cause, or had completed normally), an instance of [CancellationException] is
+ * returned. The [CancellationException.cause] of the resulting [CancellationException] references
+ * the original cancellation cause that was passed to [cancel] function.
+ *
+ * This function throws [IllegalStateException] when invoked on a job that is still active.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ public fun getCancellationException(): CancellationException
+
+ // ------------ state update ------------
+
+ /**
+ * 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`
+ * if it was already started or completed.
+ */
+ public fun start(): Boolean
+
+
+ /**
+ * Cancels this job with an optional cancellation [cause].
+ * A cause can be used to specify an error message or to provide other details on
+ * the cancellation reason for debugging purposes.
+ * See [Job] documentation for full explanation of cancellation machinery.
+ */
+ public fun cancel(cause: CancellationException? = null)
+
+ /**
+ * @suppress This method implements old version of JVM ABI. Use [cancel].
+ */
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ public fun cancel() = cancel(null)
+
+ /**
+ * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel].
+ */
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ public fun cancel(cause: Throwable? = null): Boolean
+
+ // ------------ parent-child ------------
+
+ /**
+ * Returns a sequence of this job's children.
+ *
+ * A job becomes a child of this job when it is constructed with this job in its
+ * [CoroutineContext] or using an explicit `parent` parameter.
+ *
+ * A parent-child relation has the following effect:
+ *
+ * * Cancellation of parent with [cancel] or its exceptional completion (failure)
+ * 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.
+ */
+ public val children: Sequence<Job>
+
+ /**
+ * Attaches child job so that this job becomes its parent and
+ * returns a handle that should be used to detach it.
+ *
+ * A parent-child relation has the following effect:
+ * * Cancellation of parent with [cancel] or its exceptional completion (failure)
+ * 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_ states.
+ *
+ * **A child must store the resulting [ChildHandle] and [dispose][DisposableHandle.dispose] the attachment
+ * to its parent on its own completion.**
+ *
+ * Coroutine builders and job factory functions that accept `parent` [CoroutineContext] parameter
+ * lookup a [Job] instance in the parent context and use this function to attach themselves as a child.
+ * They also store a reference to the resulting [ChildHandle] and dispose a handle when they complete.
+ *
+ * @suppress This is an internal API. This method is too error prone for public API.
+ */
+ // ChildJob and ChildHandle are made internal on purpose to further deter 3rd-party impl of Job
+ @InternalCoroutinesApi
+ public fun attachChild(child: ChildJob): ChildHandle
+
+ // ------------ state waiting ------------
+
+ /**
+ * Suspends the coroutine until this job is complete. This invocation resumes normally (without exception)
+ * when the job is complete for any reason and the [Job] of the invoking coroutine is still [active][isActive].
+ * This function also [starts][Job.start] the corresponding coroutine if the [Job] was still in _new_ state.
+ *
+ * Note that the job becomes complete only when all its children are complete.
+ *
+ * This suspending function is cancellable and **always** checks for a cancellation of the invoking coroutine's Job.
+ * If the [Job] of the invoking coroutine is cancelled or completed when this
+ * 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.
+ *
+ * This function can be used in [select] invocation with [onJoin] clause.
+ * Use [isCompleted] to check for a completion of this job without waiting.
+ *
+ * There is [cancelAndJoin] function that combines an invocation of [cancel] and `join`.
+ */
+ public suspend fun join()
+
+ /**
+ * Clause for [select] expression of [join] suspending function that selects when the job is complete.
+ * This clause never fails, even if the job completes exceptionally.
+ */
+ public val onJoin: SelectClause0
+
+ // ------------ low-level state-notification ------------
+
+ /**
+ * Registers handler that is **synchronously** invoked once on completion of this job.
+ * When the job is already complete, then the handler is immediately invoked
+ * with the job's exception or cancellation cause or `null`. Otherwise, the handler will be invoked once when this
+ * job is complete.
+ *
+ * The meaning of `cause` that is passed to the handler:
+ * * Cause is `null` when the job has completed normally.
+ * * Cause is an instance of [CancellationException] when the job was cancelled _normally_.
+ * **It should not be treated as an error**. In particular, it should not be reported to error logs.
+ * * Otherwise, the job had _failed_.
+ *
+ * The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] the
+ * registration of this handler and release its memory if its invocation is no longer needed.
+ * There is no need to dispose the handler after completion of this job. The references to
+ * all the handlers are released when this job completes.
+ *
+ * Installed [handler] should not throw any exceptions. If it does, they will get caught,
+ * wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code.
+ *
+ * **Note**: Implementation of `CompletionHandler` must be fast, non-blocking, and thread-safe.
+ * This handler can be invoked concurrently with the surrounding code.
+ * There is no guarantee on the execution context in which the [handler] is invoked.
+ */
+ public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
+
+ /**
+ * Registers handler that is **synchronously** invoked once on cancellation or completion of this job.
+ * when the job was already cancelled and is completed its execution, then the handler is immediately invoked
+ * with the job's cancellation cause or `null` unless [invokeImmediately] is set to false.
+ * Otherwise, handler will be invoked once when this job is cancelled or is complete.
+ *
+ * The meaning of `cause` that is passed to the handler:
+ * * Cause is `null` when the job has completed normally.
+ * * Cause is an instance of [CancellationException] when the job was cancelled _normally_.
+ * **It should not be treated as an error**. In particular, it should not be reported to error logs.
+ * * Otherwise, the job had _failed_.
+ *
+ * Invocation of this handler on a transition to a _cancelling_ state
+ * is controlled by [onCancelling] boolean parameter.
+ * The handler is invoked when the job becomes _cancelling_ if [onCancelling] parameter is set to `true`.
+ *
+ * The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] the
+ * registration of this handler and release its memory if its invocation is no longer needed.
+ * There is no need to dispose the handler after completion of this job. The references to
+ * all the handlers are released when this job completes.
+ *
+ * Installed [handler] should not throw any exceptions. If it does, they will get caught,
+ * wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code.
+ *
+ * **Note**: This function is a part of internal machinery that supports parent-child hierarchies
+ * and allows for implementation of suspending functions that wait on the Job's state.
+ * This function should not be used in general application code.
+ * Implementation of `CompletionHandler` must be fast, non-blocking, and thread-safe.
+ * This handler can be invoked concurrently with the surrounding code.
+ * There is no guarantee on the execution context in which the [handler] is invoked.
+ *
+ * @param onCancelling when `true`, then the [handler] is invoked as soon as this job transitions to _cancelling_ state;
+ * when `false` then the [handler] is invoked only when it transitions to _completed_ state.
+ * @param invokeImmediately when `true` and this job is already in the desired state (depending on [onCancelling]),
+ * then the [handler] is immediately and synchronously invoked and no-op [DisposableHandle] is returned;
+ * when `false` then no-op [DisposableHandle] is returned, but the [handler] is not invoked.
+ * @param handler the handler.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ public fun invokeOnCompletion(
+ onCancelling: Boolean = false,
+ invokeImmediately: Boolean = true,
+ handler: CompletionHandler): DisposableHandle
+
+ // ------------ unstable internal API ------------
+
+ /**
+ * @suppress **Error**: Operator '+' on two Job objects is meaningless.
+ * Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts.
+ * The job to the right of `+` just replaces the job the left of `+`.
+ */
+ @Suppress("DeprecatedCallableAddReplaceWith")
+ @Deprecated(message = "Operator '+' on two Job objects is meaningless. " +
+ "Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts. " +
+ "The job to the right of `+` just replaces the job the left of `+`.",
+ level = DeprecationLevel.ERROR)
+ public operator fun plus(other: Job) = other
+}
+
+/**
+ * Creates a job object in an active state.
+ * A failure of any child of this job immediately causes this job to fail, too, and cancels the rest of its children.
+ *
+ * To handle children failure independently of each other use [SupervisorJob].
+ *
+ * If [parent] job is specified, then this job becomes a child job of its parent and
+ * is cancelled when its parent fails or is cancelled. All this job's children are cancelled in this case, too.
+ * The invocation of [cancel][Job.cancel] with exception (other than [CancellationException]) on this job also cancels parent.
+ *
+ * Conceptually, the resulting job works in the same way as the job created by the `launch { body }` invocation
+ * (see [launch]), but without any code in the body. It is active until cancelled or completed. Invocation of
+ * [CompletableJob.complete] or [CompletableJob.completeExceptionally] corresponds to the successful or
+ * failed completion of the body of the coroutine.
+ *
+ * @param parent an optional parent job.
+ */
+@Suppress("FunctionName")
+public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)
+
+/** @suppress Binary compatibility only */
+@Suppress("FunctionName")
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+@JvmName("Job")
+public fun Job0(parent: Job? = null): Job = Job(parent)
+
+/**
+ * A handle to an allocated object that can be disposed to make it eligible for garbage collection.
+ */
+public interface DisposableHandle {
+ /**
+ * Disposes the corresponding object, making it eligible for garbage collection.
+ * Repeated invocation of this function has no effect.
+ */
+ public fun dispose()
+}
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@Suppress("FunctionName")
+@InternalCoroutinesApi
+public inline fun DisposableHandle(crossinline block: () -> Unit) =
+ object : DisposableHandle {
+ override fun dispose() {
+ block()
+ }
+ }
+
+// -------------------- Parent-child communication --------------------
+
+/**
+ * A reference that parent receives from its child so that it can report its cancellation.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+@InternalCoroutinesApi
+@Deprecated(level = DeprecationLevel.ERROR, message = "This is internal API and may be removed in the future releases")
+public interface ChildJob : Job {
+ /**
+ * Parent is cancelling its child by invoking this method.
+ * Child finds the cancellation cause using [ParentJob.getChildJobCancellationCause].
+ * This method does nothing is the child is already being cancelled.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public fun parentCancelled(parentJob: ParentJob)
+}
+
+/**
+ * A reference that child receives from its parent when it is being cancelled by the parent.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+@InternalCoroutinesApi
+@Deprecated(level = DeprecationLevel.ERROR, message = "This is internal API and may be removed in the future releases")
+public interface ParentJob : Job {
+ /**
+ * Child job is using this method to learn its cancellation cause when the parent cancels it with [ChildJob.parentCancelled].
+ * This method is invoked only if the child was not already being cancelled.
+ *
+ * Note that [CancellationException] is the method's return type: if child is cancelled by its parent,
+ * then the original exception is **already** handled by either the parent or the original source of failure.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public fun getChildJobCancellationCause(): CancellationException
+}
+
+/**
+ * A handle that child keep onto its parent so that it is able to report its cancellation.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+@InternalCoroutinesApi
+@Deprecated(level = DeprecationLevel.ERROR, message = "This is internal API and may be removed in the future releases")
+public interface ChildHandle : DisposableHandle {
+ /**
+ * 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,
+ * so that all its siblings and the parent can start cancelling their work asap. The second time
+ * child invokes this method when it had aggregated and determined its final cancellation cause.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public fun childCancelled(cause: Throwable): Boolean
+}
+
+// -------------------- Job extensions --------------------
+
+/**
+ * Disposes a specified [handle] when this job is complete.
+ *
+ * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
+ * ```
+ * invokeOnCompletion { handle.dispose() }
+ * ```
+ */
+internal fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle =
+ invokeOnCompletion(handler = DisposeOnCompletion(this, handle).asHandler)
+
+/**
+ * Cancels the job and suspends the invoking coroutine until the cancelled job is complete.
+ *
+ * This suspending function is cancellable and **always** checks for a cancellation of the invoking coroutine's Job.
+ * If the [Job] of the invoking coroutine is cancelled or completed when this
+ * 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.
+ *
+ * This is a shortcut for the invocation of [cancel][Job.cancel] followed by [join][Job.join].
+ */
+public suspend fun Job.cancelAndJoin() {
+ cancel()
+ return join()
+}
+
+/**
+ * Cancels all [children][Job.children] jobs of this coroutine using [Job.cancel] for all of them
+ * with an optional cancellation [cause].
+ * Unlike [Job.cancel] on this job as a whole, the state of this job itself is not affected.
+ */
+public fun Job.cancelChildren(cause: CancellationException? = null) {
+ children.forEach { it.cancel(cause) }
+}
+
+/**
+ * @suppress This method implements old version of JVM ABI. Use [cancel].
+ */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+public fun Job.cancelChildren() = cancelChildren(null)
+
+/**
+ * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [Job.cancelChildren].
+ */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+public fun Job.cancelChildren(cause: Throwable? = null) {
+ children.forEach { (it as? JobSupport)?.cancelInternal(cause) }
+}
+
+// -------------------- CoroutineContext extensions --------------------
+
+/**
+ * Returns `true` when the [Job] of the coroutine in this context is still active
+ * (has not completed and was not cancelled yet).
+ *
+ * Check this property in long-running computation loops to support cancellation
+ * when [CoroutineScope.isActive] is not available:
+ *
+ * ```
+ * while (coroutineContext.isActive) {
+ * // do some computation
+ * }
+ * ```
+ *
+ * The `coroutineContext.isActive` expression is a shortcut for `coroutineContext[Job]?.isActive == true`.
+ * See [Job.isActive].
+ */
+public val CoroutineContext.isActive: Boolean
+ get() = this[Job]?.isActive == true
+
+/**
+ * Cancels [Job] of this context with an optional cancellation cause.
+ * See [Job.cancel] for details.
+ */
+public fun CoroutineContext.cancel(cause: CancellationException? = null) {
+ this[Job]?.cancel(cause)
+}
+
+/**
+ * @suppress This method implements old version of JVM ABI. Use [CoroutineContext.cancel].
+ */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+public fun CoroutineContext.cancel() = cancel(null)
+
+/**
+ * Ensures that current job is [active][Job.isActive].
+ * If the job is no longer active, throws [CancellationException].
+ * If the job was cancelled, thrown exception contains the original cancellation cause.
+ *
+ * This method is a drop-in replacement for the following code, but with more precise exception:
+ * ```
+ * if (!job.isActive) {
+ * throw CancellationException()
+ * }
+ * ```
+ */
+public fun Job.ensureActive(): Unit {
+ if (!isActive) throw getCancellationException()
+}
+
+/**
+ * Ensures that job in the current context is [active][Job.isActive].
+ * Throws [IllegalStateException] if the context does not have a job in it.
+ *
+ * If the job is no longer active, throws [CancellationException].
+ * If the job was cancelled, thrown exception contains the original cancellation cause.
+ *
+ * This method is a drop-in replacement for the following code, but with more precise exception:
+ * ```
+ * if (!isActive) {
+ * throw CancellationException()
+ * }
+ * ```
+ */
+public fun CoroutineContext.ensureActive(): Unit {
+ val job = get(Job) ?: error("Context cannot be checked for liveness because it does not have a job: $this")
+ job.ensureActive()
+}
+
+/**
+ * Cancels current job, including all its children with a specified diagnostic error [message].
+ * A [cause] can be specified to provide additional details on a cancellation reason for debugging purposes.
+ */
+public fun Job.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause))
+
+/**
+ * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [CoroutineContext.cancel].
+ */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+public fun CoroutineContext.cancel(cause: Throwable? = null): Boolean =
+ @Suppress("DEPRECATION")
+ (this[Job] as? JobSupport)?.cancelInternal(cause) ?: false
+
+/**
+ * Cancels all children of the [Job] in this context, without touching the state of this job itself
+ * with an optional cancellation cause. See [Job.cancel].
+ * It does not do anything if there is no job in the context or it has no children.
+ */
+public fun CoroutineContext.cancelChildren(cause: CancellationException? = null) {
+ this[Job]?.children?.forEach { it.cancel(cause) }
+}
+
+/**
+ * @suppress This method implements old version of JVM ABI. Use [CoroutineContext.cancelChildren].
+ */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+public fun CoroutineContext.cancelChildren() = cancelChildren(null)
+
+/**
+ * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [CoroutineContext.cancelChildren].
+ */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+public fun CoroutineContext.cancelChildren(cause: Throwable? = null) {
+ this[Job]?.children?.forEach { (it as? JobSupport)?.cancelInternal(cause) }
+}
+
+/**
+ * No-op implementation of [DisposableHandle].
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public object NonDisposableHandle : DisposableHandle, ChildHandle {
+ /**
+ * Does not do anything.
+ * @suppress
+ */
+ override fun dispose() {}
+
+ /**
+ * Returns `false`.
+ * @suppress
+ */
+ override fun childCancelled(cause: Throwable): Boolean = false
+
+ /**
+ * Returns "NonDisposableHandle" string.
+ * @suppress
+ */
+ override fun toString(): String = "NonDisposableHandle"
+}
diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt
new file mode 100644
index 00000000..d7ca5f67
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/JobSupport.kt
@@ -0,0 +1,1423 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("DEPRECATION_ERROR")
+
+package kotlinx.coroutines
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.js.*
+import kotlin.jvm.*
+
+/**
+ * A concrete implementation of [Job]. It is optionally a child to a parent job.
+ *
+ * This is an open class designed for extension by more specific classes that might augment the
+ * state and mare store addition state information for completed jobs, like their result values.
+ *
+ * @param active when `true` the job is created in _active_ state, when `false` in _new_ state. See [Job] for details.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+@Deprecated(level = DeprecationLevel.ERROR, message = "This is internal API and may be removed in the future releases")
+public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {
+ final override val key: CoroutineContext.Key<*> get() = Job
+
+ /*
+ === Internal states ===
+
+ name state class public state description
+ ------ ------------ ------------ -----------
+ EMPTY_N EmptyNew : New no listeners
+ EMPTY_A EmptyActive : Active no listeners
+ SINGLE JobNode : Active a single listener
+ SINGLE+ JobNode : Active a single listener + NodeList added as its next
+ LIST_N InactiveNodeList : New a list of listeners (promoted once, does not got back to EmptyNew)
+ LIST_A NodeList : Active a list of listeners (promoted once, does not got back to JobNode/EmptyActive)
+ COMPLETING Finishing : Completing has a list of listeners (promoted once from LIST_*)
+ CANCELLING Finishing : Cancelling -- " --
+ FINAL_C Cancelled : Cancelled Cancelled (final state)
+ FINAL_R <any> : Completed produced some result
+
+ === Transitions ===
+
+ New states Active states Inactive states
+
+ +---------+ +---------+ }
+ | EMPTY_N | ----> | EMPTY_A | ----+ } Empty states
+ +---------+ +---------+ | }
+ | | | ^ | +----------+
+ | | | | +--> | FINAL_* |
+ | | V | | +----------+
+ | | +---------+ | }
+ | | | SINGLE | ----+ } JobNode states
+ | | +---------+ | }
+ | | | | }
+ | | V | }
+ | | +---------+ | }
+ | +-------> | SINGLE+ | ----+ }
+ | +---------+ | }
+ | | |
+ V V |
+ +---------+ +---------+ | }
+ | LIST_N | ----> | LIST_A | ----+ } [Inactive]NodeList states
+ +---------+ +---------+ | }
+ | | | | |
+ | | +--------+ | |
+ | | | V |
+ | | | +------------+ | +------------+ }
+ | +-------> | COMPLETING | --+-- | CANCELLING | } Finishing states
+ | | +------------+ +------------+ }
+ | | | ^
+ | | | |
+ +--------+---------+--------------------+
+
+
+ This state machine and its transition matrix are optimized for the common case when a job is created in active
+ state (EMPTY_A), at most one completion listener is added to it during its life-time, and it completes
+ successfully without children (in this case it directly goes from EMPTY_A or SINGLE state to FINAL_R
+ state without going to COMPLETING state)
+
+ Note that the actual `_state` variable can also be a reference to atomic operation descriptor `OpDescriptor`
+
+ ---------- TIMELINE of state changes and notification in Job lifecycle ----------
+
+ | The longest possible chain of events in shown, shorter versions cut-through intermediate states,
+ | while still performing all the notifications in this order.
+
+ + Job object is created
+ ## NEW: state == EMPTY_ACTIVE | is InactiveNodeList
+ + initParentJob / initParentJobInternal (invokes attachChild on its parent, initializes parentHandle)
+ ~ waits for start
+ >> start / join / await invoked
+ ## ACTIVE: state == EMPTY_ACTIVE | is JobNode | is NodeList
+ + onStartInternal / onStart (lazy coroutine is started)
+ ~ active coroutine is working (or scheduled to execution)
+ >> childCancelled / cancelImpl invoked
+ ## CANCELLING: state is Finishing, state.rootCause != null
+ ------ cancelling listeners are not admitted anymore, invokeOnCompletion(onCancelling=true) returns NonDisposableHandle
+ ------ new children get immediately cancelled, but are still admitted to the list
+ + onCancelling
+ + notifyCancelling (invoke all cancelling listeners -- cancel all children, suspended functions resume with exception)
+ + cancelParent (rootCause of cancellation is communicated to the parent, parent is cancelled, too)
+ ~ waits for completion of coroutine body
+ >> makeCompleting / makeCompletingOnce invoked
+ ## COMPLETING: state is Finishing, state.isCompleting == true
+ ------ new children are not admitted anymore, attachChild returns NonDisposableHandle
+ ~ waits for children
+ >> last child completes
+ - computes the final exception
+ ## SEALED: state is Finishing, state.isSealed == true
+ ------ cancel/childCancelled returns false (cannot handle exceptions anymore)
+ + cancelParent (final exception is communicated to the parent, parent incorporates it)
+ + handleJobException ("launch" StandaloneCoroutine invokes CoroutineExceptionHandler)
+ ## COMPLETE: state !is Incomplete (CompletedExceptionally | Cancelled)
+ ------ completion listeners are not admitted anymore, invokeOnCompletion returns NonDisposableHandle
+ + parentHandle.dispose
+ + notifyCompletion (invoke all completion listeners)
+ + onCompletionInternal / onCompleted / onCancelled
+
+ ---------------------------------------------------------------------------------
+ */
+
+ // Note: use shared objects while we have no listeners
+ private val _state = atomic<Any?>(if (active) EMPTY_ACTIVE else EMPTY_NEW)
+
+ @Volatile
+ @JvmField
+ internal var parentHandle: ChildHandle? = null
+
+ // ------------ initialization ------------
+
+ /**
+ * Initializes parent job.
+ * It shall be invoked at most once after construction after all other initialization.
+ */
+ internal fun initParentJobInternal(parent: Job?) {
+ assert { parentHandle == null }
+ if (parent == null) {
+ parentHandle = NonDisposableHandle
+ return
+ }
+ parent.start() // make sure the parent is started
+ @Suppress("DEPRECATION")
+ val handle = parent.attachChild(this)
+ parentHandle = handle
+ // now check our state _after_ registering (see tryFinalizeSimpleState order of actions)
+ if (isCompleted) {
+ handle.dispose()
+ parentHandle = NonDisposableHandle // release it just in case, to aid GC
+ }
+ }
+
+ // ------------ state query ------------
+ /**
+ * Returns current state of this job.
+ * If final state of the job is [Incomplete], then it is boxed into [IncompleteStateBox]
+ * and should be [unboxed][unboxState] before returning to user code.
+ */
+ internal val state: Any? get() {
+ _state.loop { state -> // helper loop on state (complete in-progress atomic operations)
+ if (state !is OpDescriptor) return state
+ state.perform(this)
+ }
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ private inline fun loopOnState(block: (Any?) -> Unit): Nothing {
+ while (true) {
+ block(state)
+ }
+ }
+
+ public override val isActive: Boolean get() {
+ val state = this.state
+ return state is Incomplete && state.isActive
+ }
+
+ public final override val isCompleted: Boolean get() = state !is Incomplete
+
+ public final override val isCancelled: Boolean get() {
+ val state = this.state
+ return state is CompletedExceptionally || (state is Finishing && state.isCancelling)
+ }
+
+ // ------------ state update ------------
+
+ // Finalizes Finishing -> Completed (terminal state) transition.
+ // ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
+ private fun tryFinalizeFinishingState(state: Finishing, proposedUpdate: Any?, mode: Int): Boolean {
+ /*
+ * Note: proposed state can be Incomplete, e.g.
+ * async {
+ * something.invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
+ * }
+ */
+ require(this.state === state) // consistency check -- it cannot change
+ require(!state.isSealed) // consistency check -- cannot be sealed yet
+ require(state.isCompleting) // consistency check -- must be marked as completing
+ val proposedException = (proposedUpdate as? CompletedExceptionally)?.cause
+ // Create the final exception and seal the state so that no more exceptions can be added
+ var wasCancelling = false // KLUDGE: we cannot have contract for our own expect fun synchronized
+ val finalException = synchronized(state) {
+ wasCancelling = state.isCancelling
+ val exceptions = state.sealLocked(proposedException)
+ val finalCause = getFinalRootCause(state, exceptions)
+ if (finalCause != null) addSuppressedExceptions(finalCause, exceptions)
+ finalCause
+ }
+ // Create the final state object
+ val finalState = when {
+ // was not cancelled (no exception) -> use proposed update value
+ finalException == null -> proposedUpdate
+ // small optimization when we can used proposeUpdate object as is on cancellation
+ finalException === proposedException -> proposedUpdate
+ // cancelled job final state
+ else -> CompletedExceptionally(finalException)
+ }
+ // Now handle the final exception
+ if (finalException != null) {
+ val handled = cancelParent(finalException) || handleJobException(finalException)
+ if (handled) (finalState as CompletedExceptionally).makeHandled()
+ }
+ // Process state updates for the final state before the state of the Job is actually set to the final state
+ // to avoid races where outside observer may see the job in the final state, yet exception is not handled yet.
+ if (!wasCancelling) onCancelling(finalException)
+ onCompletionInternal(finalState)
+ // Then CAS to completed state -> it must succeed
+ require(_state.compareAndSet(state, finalState.boxIncomplete())) { "Unexpected state: ${_state.value}, expected: $state, update: $finalState" }
+ // And process all post-completion actions
+ completeStateFinalization(state, finalState, mode)
+ return true
+ }
+
+ private fun getFinalRootCause(state: Finishing, exceptions: List<Throwable>): Throwable? {
+ // A case of no exceptions
+ if (exceptions.isEmpty()) {
+ // materialize cancellation exception if it was not materialized yet
+ if (state.isCancelling) return createJobCancellationException()
+ return null
+ }
+ // Take either the first real exception (not a cancellation) or just the first exception
+ return exceptions.firstOrNull { it !is CancellationException } ?: exceptions[0]
+ }
+
+ private fun addSuppressedExceptions(rootCause: Throwable, exceptions: List<Throwable>) {
+ if (exceptions.size <= 1) return // nothing more to do here
+ val seenExceptions = identitySet<Throwable>(exceptions.size)
+ /*
+ * Note that root cause may be a recovered exception as well.
+ * To avoid cycles we unwrap the root cause and check for self-suppression against unwrapped cause,
+ * but add suppressed exceptions to the recovered root cause (as it is our final exception)
+ */
+ val unwrappedCause = unwrap(rootCause)
+ for (exception in exceptions) {
+ val unwrapped = unwrap(exception)
+ if (unwrapped !== rootCause && unwrapped !== unwrappedCause &&
+ unwrapped !is CancellationException && seenExceptions.add(unwrapped)) {
+ rootCause.addSuppressedThrowable(unwrapped)
+ }
+ }
+ }
+
+ // fast-path method to finalize normally completed coroutines without children
+ private fun tryFinalizeSimpleState(state: Incomplete, update: Any?, mode: Int): Boolean {
+ 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
+ onCompletionInternal(update)
+ completeStateFinalization(state, update, mode)
+ return true
+ }
+
+ // suppressed == true when any exceptions were suppressed while building the final completion cause
+ private fun completeStateFinalization(state: Incomplete, update: Any?, mode: Int) {
+ /*
+ * Now the job in THE FINAL state. We need to properly handle the resulting state.
+ * Order of various invocations here is important.
+ *
+ * 1) Unregister from parent job.
+ */
+ parentHandle?.let {
+ it.dispose() // volatile read parentHandle _after_ state was updated
+ parentHandle = NonDisposableHandle // release it just in case, to aid GC
+ }
+ val cause = (update as? CompletedExceptionally)?.cause
+ /*
+ * 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)
+ try {
+ state.invoke(cause)
+ } catch (ex: Throwable) {
+ handleOnCompletionException(CompletionHandlerException("Exception in completion handler $state for $this", ex))
+ }
+ } else {
+ state.list?.notifyCompletion(cause)
+ }
+ /*
+ * 3) Resumes the rest of the code in scoped coroutines
+ * (runBlocking, coroutineScope, withContext, withTimeout, etc)
+ */
+ afterCompletionInternal(update, mode)
+ }
+
+ private fun notifyCancelling(list: NodeList, cause: Throwable) {
+ // first cancel our own children
+ onCancelling(cause)
+ notifyHandlers<JobCancellingNode<*>>(list, cause)
+ // then cancel parent
+ cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
+ }
+
+ /**
+ * The method that is invoked when the job is cancelled to possibly propagate cancellation to the parent.
+ * Returns `true` if the parent is responsible for handling the exception, `false` otherwise.
+ *
+ * Invariant: never returns `false` for instances of [CancellationException], otherwise such exception
+ * may leak to the [CoroutineExceptionHandler].
+ */
+ private fun cancelParent(cause: Throwable): Boolean {
+ // Is scoped coroutine -- don't propagate, will be rethrown
+ if (isScopedCoroutine) return true
+
+ /* CancellationException is considered "normal" and parent usually is not cancelled when child produces it.
+ * This allow parent to cancel its children (normally) without being cancelled itself, unless
+ * child crashes and produce some other exception during its completion.
+ */
+ val isCancellation = cause is CancellationException
+ val parent = parentHandle
+ // No parent -- ignore CE, report other exceptions.
+ if (parent === null || parent === NonDisposableHandle) {
+ return isCancellation
+ }
+
+ // Notify parent but don't forget to check cancellation
+ return parent.childCancelled(cause) || isCancellation
+ }
+
+ private fun NodeList.notifyCompletion(cause: Throwable?) =
+ notifyHandlers<JobNode<*>>(this, cause)
+
+ private inline fun <reified T: JobNode<*>> notifyHandlers(list: NodeList, cause: Throwable?) {
+ var exception: Throwable? = null
+ list.forEach<T> { node ->
+ try {
+ node.invoke(cause)
+ } catch (ex: Throwable) {
+ exception?.apply { addSuppressedThrowable(ex) } ?: run {
+ exception = CompletionHandlerException("Exception in completion handler $node for $this", ex)
+ }
+ }
+ }
+ exception?.let { handleOnCompletionException(it) }
+ }
+
+ public final override fun start(): Boolean {
+ loopOnState { state ->
+ when (startInternal(state)) {
+ FALSE -> return false
+ TRUE -> return true
+ }
+ }
+ }
+
+ // returns: RETRY/FALSE/TRUE:
+ // FALSE when not new,
+ // TRUE when started
+ // RETRY when need to retry
+ private fun startInternal(state: Any?): Int {
+ when (state) {
+ is Empty -> { // EMPTY_X state -- no completion handlers
+ if (state.isActive) return FALSE // already active
+ if (!_state.compareAndSet(state, EMPTY_ACTIVE)) return RETRY
+ onStartInternal()
+ return TRUE
+ }
+ is InactiveNodeList -> { // LIST state -- inactive with a list of completion handlers
+ if (!_state.compareAndSet(state, state.list)) return RETRY
+ onStartInternal()
+ return TRUE
+ }
+ else -> return FALSE // not a new state
+ }
+ }
+
+ /**
+ * Override to provide the actual [start] action.
+ * This function is invoked exactly once when non-active coroutine is [started][start].
+ */
+ internal open fun onStartInternal() {}
+
+ public final override fun getCancellationException(): CancellationException =
+ when (val state = this.state) {
+ is Finishing -> state.rootCause?.toCancellationException("$classSimpleName is cancelling")
+ ?: error("Job is still new or active: $this")
+ is Incomplete -> error("Job is still new or active: $this")
+ is CompletedExceptionally -> state.cause.toCancellationException()
+ else -> JobCancellationException("$classSimpleName has completed normally", null, this)
+ }
+
+ protected fun Throwable.toCancellationException(message: String? = null): CancellationException =
+ this as? CancellationException ?:
+ JobCancellationException(message ?: "$classSimpleName was cancelled", this, this@JobSupport)
+
+ /**
+ * Returns the cause that signals the completion of this job -- it returns the original
+ * [cancel] cause, [CancellationException] or **`null` if this job had completed normally**.
+ * This function throws [IllegalStateException] when invoked for an job that has not [completed][isCompleted] nor
+ * is being cancelled yet.
+ */
+ protected val completionCause: Throwable?
+ get() = when (val state = state) {
+ is Finishing -> state.rootCause
+ ?: error("Job is still new or active: $this")
+ is Incomplete -> error("Job is still new or active: $this")
+ is CompletedExceptionally -> state.cause
+ else -> null
+ }
+
+ /**
+ * Returns `true` when [completionCause] exception was handled by parent coroutine.
+ */
+ protected val completionCauseHandled: Boolean
+ get() = state.let { it is CompletedExceptionally && it.handled }
+
+ @Suppress("OverridingDeprecatedMember")
+ public final override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle =
+ invokeOnCompletion(onCancelling = false, invokeImmediately = true, handler = handler)
+
+ public final override fun invokeOnCompletion(
+ onCancelling: Boolean,
+ invokeImmediately: Boolean,
+ handler: CompletionHandler
+ ): DisposableHandle {
+ var nodeCache: JobNode<*>? = null
+ 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
+ }
+ is Incomplete -> {
+ val list = state.list
+ if (list == null) { // SINGLE/SINGLE+
+ promoteSingleToNodeList(state as JobNode<*>)
+ } else {
+ var rootCause: Throwable? = null
+ var handle: DisposableHandle = NonDisposableHandle
+ if (onCancelling && state is Finishing) {
+ synchronized(state) {
+ // check if we are installing cancellation handler on job that is being cancelled
+ rootCause = state.rootCause // != null if cancelling job
+ // We add node to the list in two cases --- either the job is not being cancelled
+ // 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
+ // otherwise handler is invoked immediately out of the synchronized section & handle returned
+ handle = node
+ }
+ }
+ }
+ if (rootCause != null) {
+ // Note: attachChild uses invokeImmediately, so it gets invoked when adding to cancelled job
+ if (invokeImmediately) handler.invokeIt(rootCause)
+ return handle
+ } else {
+ val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
+ if (addLastAtomic(state, list, node)) return node
+ }
+ }
+ }
+ else -> { // is complete
+ // :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension,
+ // because we play type tricks on Kotlin/JS and handler is not necessarily a function there
+ if (invokeImmediately) handler.invokeIt((state as? CompletedExceptionally)?.cause)
+ return NonDisposableHandle
+ }
+ }
+ }
+ }
+
+ private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode<*> {
+ return if (onCancelling)
+ (handler as? JobCancellingNode<*>)?.also { require(it.job === this) }
+ ?: InvokeOnCancelling(this, handler)
+ else
+ (handler as? JobNode<*>)?.also { require(it.job === this && it !is JobCancellingNode) }
+ ?: InvokeOnCompletion(this, handler)
+ }
+
+ private fun addLastAtomic(expect: Any, list: NodeList, node: JobNode<*>) =
+ list.addLastIf(node) { this.state === expect }
+
+ private fun promoteEmptyToNodeList(state: Empty) {
+ // try to promote it to LIST state with the corresponding state
+ val list = NodeList()
+ val update = if (state.isActive) list else InactiveNodeList(list)
+ _state.compareAndSet(state, update)
+ }
+
+ 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)
+ val list = state.nextNode // either our NodeList or somebody else won the race, updated state
+ // just attempt converting it to list if state is still the same, then we'll continue lock-free loop
+ _state.compareAndSet(state, list)
+ }
+
+ public final override suspend fun join() {
+ if (!joinInternal()) { // fast-path no wait
+ coroutineContext.checkCompletion()
+ return // do not suspend
+ }
+ return joinSuspend() // slow-path wait
+ }
+
+ private fun joinInternal(): Boolean {
+ loopOnState { state ->
+ if (state !is Incomplete) return false // not active anymore (complete) -- no need to wait
+ if (startInternal(state) >= 0) return true // wait unless need to retry
+ }
+ }
+
+ 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))
+ }
+
+ public final override val onJoin: SelectClause0
+ get() = this
+
+ // registerSelectJoin
+ public final override fun <R> registerSelectClause0(select: SelectInstance<R>, block: suspend () -> R) {
+ // fast-path -- check state and select/return if needed
+ loopOnState { state ->
+ if (select.isSelected) return
+ if (state !is Incomplete) {
+ // already complete -- select result
+ if (select.trySelect(null)) {
+ block.startCoroutineUnintercepted(select.completion)
+ }
+ return
+ }
+ if (startInternal(state) == 0) {
+ // slow-path -- register waiter for completion
+ select.disposeOnSelect(invokeOnCompletion(handler = SelectJoinOnCompletion(this, select, block).asHandler))
+ return
+ }
+ }
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ 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
+ 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
+ }
+ is Incomplete -> { // may have a list of completion handlers
+ // remove node from the list if there is a list
+ if (state.list != null) node.remove()
+ return
+ }
+ else -> return // it is complete and does not have any completion handlers
+ }
+ }
+ }
+
+ /**
+ * Returns `true` for job that do not have "body block" to complete and should immediately go into
+ * completing state and start waiting for children.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal open val onCancelComplete: Boolean get() = false
+
+ // external cancel with cause, never invoked implicitly from internal machinery
+ public override fun cancel(cause: CancellationException?) {
+ cancelInternal(cause) // must delegate here, because some classes override cancelInternal(x)
+ }
+
+ // HIDDEN in Job interface. Invoked only by legacy compiled code.
+ // external cancel with (optional) cause, never invoked implicitly from internal machinery
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Added since 1.2.0 for binary compatibility with versions <= 1.1.x")
+ public override fun cancel(cause: Throwable?): Boolean =
+ cancelInternal(cause)
+
+ // It is overridden in channel-linked implementation
+ // Note: Boolean result is used only in HIDDEN DEPRECATED functions that were public in versions <= 1.1.x
+ public open fun cancelInternal(cause: Throwable?): Boolean =
+ cancelImpl(cause) && handlesException
+
+ // Parent is cancelling child
+ public final override fun parentCancelled(parentJob: ParentJob) {
+ cancelImpl(parentJob)
+ }
+
+ /**
+ * Child was cancelled with a cause.
+ * In this method parent decides whether it cancels itself (e.g. on a critical failure) and whether it handles the exception of the child.
+ * It is overridden in supervisor implementations to completely ignore any child cancellation.
+ * Returns `true` if exception is handled, `false` otherwise (then caller is responsible for handling an exception)
+ *
+ * Invariant: never returns `false` for instances of [CancellationException], otherwise such exception
+ * may leak to the [CoroutineExceptionHandler].
+ */
+ public open fun childCancelled(cause: Throwable): Boolean {
+ if (cause is CancellationException) return true
+ return cancelImpl(cause) && handlesException
+ }
+
+ /**
+ * Makes this [Job] cancelled with a specified [cause].
+ * It is used in [AbstractCoroutine]-derived classes when there is an internal failure.
+ */
+ public fun cancelCoroutine(cause: Throwable?) = cancelImpl(cause)
+
+ // cause is Throwable or ParentJob when cancelChild was invoked
+ // returns true is exception was handled, false otherwise
+ internal fun cancelImpl(cause: Any?): Boolean {
+ if (onCancelComplete) {
+ // make sure it is completing, if cancelMakeCompleting returns true it means it had make it
+ // completing and had recorded exception
+ if (cancelMakeCompleting(cause)) return true
+ // otherwise just record exception via makeCancelling below
+ }
+ return makeCancelling(cause)
+ }
+
+ // cause is Throwable or ParentJob when cancelChild was invoked
+ private fun cancelMakeCompleting(cause: Any?): Boolean {
+ loopOnState { state ->
+ if (state !is Incomplete || state is Finishing && state.isCompleting) {
+ return false // already completed/completing, do not even propose update
+ }
+ val proposedUpdate = CompletedExceptionally(createCauseException(cause))
+ when (tryMakeCompleting(state, proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) {
+ COMPLETING_ALREADY_COMPLETING -> return false
+ COMPLETING_COMPLETED, COMPLETING_WAITING_CHILDREN -> return true
+ COMPLETING_RETRY -> return@loopOnState
+ else -> error("unexpected result")
+ }
+ }
+ }
+
+ private fun createJobCancellationException() =
+ JobCancellationException("Job was cancelled", null, this)
+
+ override fun getChildJobCancellationCause(): CancellationException {
+ // determine root cancellation cause of this job (why is it cancelling its children?)
+ val state = this.state
+ val rootCause = when (state) {
+ is Finishing -> state.rootCause
+ is CompletedExceptionally -> state.cause
+ is Incomplete -> error("Cannot be cancelling child in this state: $state")
+ else -> null // create exception with the below code on normal completion
+ }
+ return (rootCause as? CancellationException) ?: JobCancellationException("Parent job is ${stateString(state)}", rootCause, this)
+ }
+
+ // cause is Throwable or ParentJob when cancelChild was invoked
+ private fun createCauseException(cause: Any?): Throwable = when (cause) {
+ is Throwable? -> cause ?: createJobCancellationException()
+ else -> (cause as ParentJob).getChildJobCancellationCause()
+ }
+
+ // transitions to Cancelling state
+ // cause is Throwable or ParentJob when cancelChild was invoked
+ private fun makeCancelling(cause: Any?): Boolean {
+ var causeExceptionCache: Throwable? = null // lazily init result of createCauseException(cause)
+ loopOnState { state ->
+ when (state) {
+ is Finishing -> { // already finishing -- collect exceptions
+ val notifyRootCause = synchronized(state) {
+ if (state.isSealed) return false // too late, already sealed -- cannot add exception nor mark cancelled
+ // add exception, do nothing is parent is cancelling child that is already being cancelled
+ val wasCancelling = state.isCancelling // will notify if was not cancelling
+ // Materialize missing exception if it is the first exception (otherwise -- don't)
+ if (cause != null || !wasCancelling) {
+ val causeException = causeExceptionCache ?: createCauseException(cause).also { causeExceptionCache = it }
+ state.addExceptionLocked(causeException)
+ }
+ // take cause for notification if was not in cancelling state before
+ state.rootCause.takeIf { !wasCancelling }
+ }
+ notifyRootCause?.let { notifyCancelling(state.list, it) }
+ return true
+ }
+ is Incomplete -> {
+ // Not yet finishing -- try to make it cancelling
+ val causeException = causeExceptionCache ?: createCauseException(cause).also { causeExceptionCache = it }
+ if (state.isActive) {
+ // active state becomes cancelling
+ if (tryMakeCancelling(state, causeException)) return true
+ } else {
+ // non active state starts completing
+ when (tryMakeCompleting(state, CompletedExceptionally(causeException), mode = MODE_ATOMIC_DEFAULT)) {
+ COMPLETING_ALREADY_COMPLETING -> error("Cannot happen in $state")
+ COMPLETING_COMPLETED, COMPLETING_WAITING_CHILDREN -> return true // ok
+ COMPLETING_RETRY -> return@loopOnState
+ else -> error("unexpected result")
+ }
+ }
+ }
+ else -> return false // already complete
+ }
+ }
+ }
+
+ // Performs promotion of incomplete coroutine state to NodeList for the purpose of
+ // converting coroutine state to Cancelling, returns null when need to retry
+ 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<*> -> {
+ // SINGLE/SINGLE+ must be promoted to NodeList first, because otherwise we cannot
+ // correctly capture a reference to it
+ promoteSingleToNodeList(state)
+ null // retry
+ }
+ else -> error("State should have list: $state")
+ }
+
+ // try make new Cancelling state on the condition that we're still in the expected state
+ private fun tryMakeCancelling(state: Incomplete, rootCause: Throwable): Boolean {
+ assert { state !is Finishing } // only for non-finishing states
+ assert { state.isActive } // only for active states
+ // get state's list or else promote to list to correctly operate on child lists
+ val list = getOrPromoteCancellingList(state) ?: return false
+ // Create cancelling state (with rootCause!)
+ val cancelling = Finishing(list, false, rootCause)
+ if (!_state.compareAndSet(state, cancelling)) return false
+ // Notify listeners
+ notifyCancelling(list, rootCause)
+ return true
+ }
+
+ /**
+ * This function is used by [CompletableDeferred.complete] (and exceptionally) and by [JobImpl.cancel].
+ * It returns `false` on repeated invocation (when this job is already completing).
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal fun makeCompleting(proposedUpdate: Any?): Boolean = loopOnState { state ->
+ when (tryMakeCompleting(state, proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) {
+ COMPLETING_ALREADY_COMPLETING -> return false
+ COMPLETING_COMPLETED, COMPLETING_WAITING_CHILDREN -> return true
+ COMPLETING_RETRY -> return@loopOnState
+ else -> error("unexpected result")
+ }
+ }
+
+ /**
+ * This function is used by [AbstractCoroutine.resume].
+ * It throws exception on repeated invocation (when this job is already completing).
+ *
+ * Returns:
+ * * `true` if state was updated to completed/cancelled;
+ * * `false` if made completing or it is cancelling and is waiting for children.
+ *
+ * @throws IllegalStateException if job is already complete or completing
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal fun makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean = loopOnState { state ->
+ when (tryMakeCompleting(state, proposedUpdate, mode)) {
+ COMPLETING_ALREADY_COMPLETING -> throw IllegalStateException("Job $this is already complete or completing, " +
+ "but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull)
+ COMPLETING_COMPLETED -> return true
+ COMPLETING_WAITING_CHILDREN -> return false
+ COMPLETING_RETRY -> return@loopOnState
+ else -> error("unexpected result")
+ }
+ }
+
+ private fun tryMakeCompleting(state: Any?, proposedUpdate: Any?, mode: Int): Int {
+ if (state !is Incomplete)
+ return COMPLETING_ALREADY_COMPLETING
+ /*
+ * FAST PATH -- no children to wait for && simple state (no list) && not cancelling => can complete immediately
+ * Cancellation (failures) always have to go through Finishing state to serialize exception handling.
+ * 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 (!tryFinalizeSimpleState(state, proposedUpdate, mode)) return COMPLETING_RETRY
+ return COMPLETING_COMPLETED
+ }
+ // The separate slow-path function to simplify profiling
+ return tryMakeCompletingSlowPath(state, proposedUpdate, mode)
+ }
+
+ private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?, mode: Int): Int {
+ // get state's list or else promote to list to correctly operate on child lists
+ val list = getOrPromoteCancellingList(state) ?: return COMPLETING_RETRY
+ // promote to Finishing state if we are not in it yet
+ // This promotion has to be atomic w.r.t to state change, so that a coroutine that is not active yet
+ // atomically transition to finishing & completing state
+ val finishing = state as? Finishing ?: Finishing(list, false, null)
+ // must synchronize updates to finishing state
+ var notifyRootCause: Throwable? = null
+ synchronized(finishing) {
+ // check if this state is already completing
+ if (finishing.isCompleting) return COMPLETING_ALREADY_COMPLETING
+ // mark as completing
+ finishing.isCompleting = true
+ // if we need to promote to finishing then atomically do it here.
+ // We do it as early is possible while still holding the lock. This ensures that we cancelImpl asap
+ // (if somebody else is faster) and we synchronize all the threads on this finishing lock asap.
+ if (finishing !== state) {
+ if (!_state.compareAndSet(state, finishing)) return COMPLETING_RETRY
+ }
+ // ## IMPORTANT INVARIANT: Only one thread (that had set isCompleting) can go past this point
+ require(!finishing.isSealed) // cannot be sealed
+ // add new proposed exception to the finishing state
+ val wasCancelling = finishing.isCancelling
+ (proposedUpdate as? CompletedExceptionally)?.let { finishing.addExceptionLocked(it.cause) }
+ // If it just becomes cancelling --> must process cancelling notifications
+ notifyRootCause = finishing.rootCause.takeIf { !wasCancelling }
+ }
+ // process cancelling notification here -- it cancels all the children _before_ we start to to wait them (sic!!!)
+ notifyRootCause?.let { notifyCancelling(list, it) }
+ // now wait for children
+ val child = firstChild(state)
+ if (child != null && tryWaitForChild(finishing, child, proposedUpdate))
+ return COMPLETING_WAITING_CHILDREN
+ // otherwise -- we have not children left (all were already cancelled?)
+ if (tryFinalizeFinishingState(finishing, proposedUpdate, mode))
+ return COMPLETING_COMPLETED
+ // otherwise retry
+ return COMPLETING_RETRY
+ }
+
+ private val Any?.exceptionOrNull: Throwable?
+ get() = (this as? CompletedExceptionally)?.cause
+
+ private fun firstChild(state: Incomplete) =
+ state as? ChildHandleNode ?: state.list?.nextChild()
+
+ // return false when there is no more incomplete children to wait
+ // ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
+ private tailrec fun tryWaitForChild(state: Finishing, child: ChildHandleNode, proposedUpdate: Any?): Boolean {
+ val handle = child.childJob.invokeOnCompletion(
+ invokeImmediately = false,
+ handler = ChildCompletion(this, state, child, proposedUpdate).asHandler
+ )
+ if (handle !== NonDisposableHandle) return true // child is not complete and we've started waiting for it
+ val nextChild = child.nextChild() ?: return false
+ return tryWaitForChild(state, nextChild, proposedUpdate)
+ }
+
+ // ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
+ private fun continueCompleting(state: Finishing, lastChild: ChildHandleNode, proposedUpdate: Any?) {
+ require(this.state === state) // consistency check -- it cannot change while we are waiting for children
+ // figure out if we need to wait for next child
+ val waitChild = lastChild.nextChild()
+ // try wait for next child
+ if (waitChild != null && tryWaitForChild(state, waitChild, proposedUpdate)) return // waiting for next child
+ // no more children to wait -- try update state
+ if (tryFinalizeFinishingState(state, proposedUpdate, MODE_ATOMIC_DEFAULT)) return
+ }
+
+ private fun LockFreeLinkedListNode.nextChild(): ChildHandleNode? {
+ var cur = this
+ while (cur.isRemoved) cur = cur.prevNode // rollback to prev non-removed (or list head)
+ while (true) {
+ cur = cur.nextNode
+ if (cur.isRemoved) continue
+ if (cur is ChildHandleNode) return cur
+ if (cur is NodeList) return null // checked all -- no more children
+ }
+ }
+
+ public final override val children: Sequence<Job> get() = sequence {
+ when (val state = this@JobSupport.state) {
+ is ChildHandleNode -> yield(state.childJob)
+ is Incomplete -> state.list?.let { list ->
+ list.forEach<ChildHandleNode> { yield(it.childJob) }
+ }
+ }
+ }
+
+ @Suppress("OverridingDeprecatedMember")
+ public final override fun attachChild(child: ChildJob): ChildHandle {
+ /*
+ * Note: This function attaches a special ChildHandleNode node object. This node object
+ * is handled in a special way on completion on the coroutine (we wait for all of them) and
+ * is handled specially by invokeOnCompletion itself -- it adds this node to the list even
+ * if the job is already cancelling. For cancelling state child is attached under state lock.
+ * It's required to properly wait all children before completion and provide linearizable hierarchy view:
+ * 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
+ }
+
+ /**
+ * Override to process any exceptions that were encountered while invoking completion handlers
+ * installed via [invokeOnCompletion].
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal open fun handleOnCompletionException(exception: Throwable) {
+ throw exception
+ }
+
+ /**
+ * This function is invoked once as soon as this job is being cancelled for any reason or completes,
+ * similarly to [invokeOnCompletion] with `onCancelling` set to `true`.
+ *
+ * The meaning of [cause] parameter:
+ * * Cause is `null` when the job has completed normally.
+ * * Cause is an instance of [CancellationException] when the job was cancelled _normally_.
+ * **It should not be treated as an error**. In particular, it should not be reported to error logs.
+ * * Otherwise, the job had been cancelled or failed with exception.
+ *
+ * The specified [cause] is not the final cancellation cause of this job.
+ * A job may produce other exceptions while it is failing and the final cause might be different.
+ *
+ * @suppress **This is unstable API and it is subject to change.*
+ */
+ protected open fun onCancelling(cause: Throwable?) {}
+
+ /**
+ * Returns `true` for scoped coroutines.
+ * Scoped coroutine is a coroutine that is executed sequentially within the enclosing scope without any concurrency.
+ * Scoped coroutines always handle any exception happened within -- they just rethrow it to the enclosing scope.
+ * Examples of scoped coroutines are `coroutineScope`, `withTimeout` and `runBlocking`.
+ */
+ protected open val isScopedCoroutine: Boolean get() = false
+
+ /**
+ * Returns `true` for jobs that handle their exceptions or integrate them into the job's result via [onCompletionInternal].
+ * A valid implementation of this getter should recursively check parent as well before returning `false`.
+ *
+ * The only instance of the [Job] that does not handle its exceptions is [JobImpl] and its subclass [SupervisorJobImpl].
+ * @suppress **This is unstable API and it is subject to change.*
+ */
+ internal open val handlesException: Boolean get() = true
+
+ /**
+ * Handles the final job [exception] that was not handled by the parent coroutine.
+ * Returns `true` if it handles exception (so handling at later stages is not needed).
+ * It is designed to be overridden by launch-like coroutines
+ * (`StandaloneCoroutine` and `ActorCoroutine`) that don't have a result type
+ * that can represent exceptions.
+ *
+ * This method is invoked **exactly once** when the final exception of the job is determined
+ * and before it becomes complete. At the moment of invocation the job and all its children are complete.
+ */
+ protected open fun handleJobException(exception: Throwable): Boolean = false
+
+ /**
+ * Override for completion actions that need to update some external object depending on job's state,
+ * right before all the waiters for coroutine's completion are notified.
+ *
+ * @param state the final state.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun onCompletionInternal(state: Any?) {}
+
+ /**
+ * Override for the very last action on job's completion to resume the rest of the code in scoped coroutines.
+ *
+ * @param state the final state.
+ * @param mode completion mode.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun afterCompletionInternal(state: Any?, mode: Int) {}
+
+ // for nicer debugging
+ public override fun toString(): String =
+ "${toDebugString()}@$hexAddress"
+
+ @InternalCoroutinesApi
+ public fun toDebugString(): String = "${nameString()}{${stateString(state)}}"
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal open fun nameString(): String = classSimpleName
+
+ private fun stateString(state: Any?): String = when (state) {
+ is Finishing -> when {
+ state.isCancelling -> "Cancelling"
+ state.isCompleting -> "Completing"
+ else -> "Active"
+ }
+ is Incomplete -> if (state.isActive) "Active" else "New"
+ is CompletedExceptionally -> "Cancelled"
+ else -> "Completed"
+ }
+
+ // Completing & Cancelling states,
+ // All updates are guarded by synchronized(this), reads are volatile
+ @Suppress("UNCHECKED_CAST")
+ private class Finishing(
+ override val list: NodeList,
+ @Volatile
+ @JvmField var isCompleting: Boolean,
+ @Volatile
+ @JvmField var rootCause: Throwable? // NOTE: rootCause is kept even when SEALED
+ ) : SynchronizedObject(), Incomplete {
+ @Volatile
+ private var _exceptionsHolder: Any? = null // Contains null | Throwable | ArrayList | SEALED
+
+ // NotE: cannot be modified when sealed
+ val isSealed: Boolean get() = _exceptionsHolder === SEALED
+ val isCancelling: Boolean get() = rootCause != null
+ override val isActive: Boolean get() = rootCause == null // !isCancelling
+
+ // Seals current state and returns list of exceptions
+ // guarded by `synchronized(this)`
+ fun sealLocked(proposedException: Throwable?): List<Throwable> {
+ val list = when(val eh = _exceptionsHolder) { // volatile read
+ null -> allocateList()
+ is Throwable -> allocateList().also { it.add(eh) }
+ is ArrayList<*> -> eh as ArrayList<Throwable>
+ else -> error("State is $eh") // already sealed -- cannot happen
+ }
+ val rootCause = this.rootCause // volatile read
+ rootCause?.let { list.add(0, it) } // note -- rootCause goes to the beginning
+ if (proposedException != null && proposedException != rootCause) list.add(proposedException)
+ _exceptionsHolder = SEALED
+ return list
+ }
+
+ // guarded by `synchronized(this)`
+ fun addExceptionLocked(exception: Throwable) {
+ val rootCause = this.rootCause // volatile read
+ if (rootCause == null) {
+ this.rootCause = exception
+ return
+ }
+ if (exception === rootCause) return // nothing to do
+ when (val eh = _exceptionsHolder) { // volatile read
+ null -> _exceptionsHolder = exception
+ is Throwable -> {
+ if (exception === eh) return // nothing to do
+ _exceptionsHolder = allocateList().apply {
+ add(eh)
+ add(exception)
+
+ }
+ }
+ is ArrayList<*> -> (eh as ArrayList<Throwable>).add(exception)
+ else -> error("State is $eh") // already sealed -- cannot happen
+ }
+ }
+
+ private fun allocateList() = ArrayList<Throwable>(4)
+
+ override fun toString(): String =
+ "Finishing[cancelling=$isCancelling, completing=$isCompleting, rootCause=$rootCause, exceptions=$_exceptionsHolder, list=$list]"
+ }
+
+ private val Incomplete.isCancelling: Boolean
+ get() = this is Finishing && isCancelling
+
+ // Used by parent that is waiting for child completion
+ private class ChildCompletion(
+ private val parent: JobSupport,
+ private val state: Finishing,
+ private val child: ChildHandleNode,
+ private val proposedUpdate: Any?
+ ) : JobNode<Job>(child.childJob) {
+ override fun invoke(cause: Throwable?) {
+ parent.continueCompleting(state, child, proposedUpdate)
+ }
+ override fun toString(): String =
+ "ChildCompletion[$child, $proposedUpdate]"
+ }
+
+ private class AwaitContinuation<T>(
+ delegate: Continuation<T>,
+ private val job: JobSupport
+ ) : CancellableContinuationImpl<T>(delegate, MODE_CANCELLABLE) {
+ override fun getContinuationCancellationCause(parent: Job): Throwable {
+ val state = job.state
+ /*
+ * When the job we are waiting for had already completely completed exceptionally or
+ * is failing, we shall use its root/completion cause for await's result.
+ */
+ if (state is Finishing) state.rootCause?.let { return it }
+ if (state is CompletedExceptionally) return state.cause
+ return parent.getCancellationException()
+ }
+
+ protected override fun nameString(): String =
+ "AwaitContinuation"
+ }
+
+ /*
+ * =================================================================================================
+ * This is ready-to-use implementation for Deferred interface.
+ * However, it is not type-safe. Conceptually it just exposes the value of the underlying
+ * completed state as `Any?`
+ * =================================================================================================
+ */
+
+ public val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally
+
+ public fun getCompletionExceptionOrNull(): Throwable? {
+ val state = this.state
+ check(state !is Incomplete) { "This job has not completed yet" }
+ return state.exceptionOrNull
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal fun getCompletedInternal(): Any? {
+ val state = this.state
+ check(state !is Incomplete) { "This job has not completed yet" }
+ if (state is CompletedExceptionally) throw state.cause
+ return state.unboxState()
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal suspend fun awaitInternal(): Any? {
+ // fast-path -- check state (avoid extra object creation)
+ while (true) { // lock-free loop on state
+ val state = this.state
+ if (state !is Incomplete) {
+ // already complete -- just return result
+ if (state is CompletedExceptionally) { // Slow path to recover stacktrace
+ recoverAndThrow(state.cause)
+ }
+ return state.unboxState()
+
+ }
+ if (startInternal(state) >= 0) break // break unless needs to retry
+ }
+ return awaitSuspend() // slow-path
+ }
+
+ private suspend fun awaitSuspend(): Any? = suspendCoroutineUninterceptedOrReturn { uCont ->
+ /*
+ * Custom code here, so that parent coroutine that is using await
+ * on its child deferred (async) coroutine would throw the exception that this child had
+ * thrown and not a JobCancellationException.
+ */
+ val cont = AwaitContinuation(uCont.intercepted(), this)
+ cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler))
+ cont.getResult()
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ // registerSelectAwaitInternal
+ @Suppress("UNCHECKED_CAST")
+ internal fun <T, R> registerSelectClause1Internal(select: SelectInstance<R>, block: suspend (T) -> R) {
+ // fast-path -- check state and select/return if needed
+ loopOnState { state ->
+ if (select.isSelected) return
+ if (state !is Incomplete) {
+ // already complete -- select result
+ if (select.trySelect(null)) {
+ if (state is CompletedExceptionally) {
+ select.resumeSelectCancellableWithException(state.cause)
+ }
+ else {
+ block.startCoroutineUnintercepted(state.unboxState() as T, select.completion)
+ }
+ }
+ return
+ }
+ if (startInternal(state) == 0) {
+ // slow-path -- register waiter for completion
+ select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(this, select, block).asHandler))
+ return
+ }
+ }
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @Suppress("UNCHECKED_CAST")
+ internal fun <T, R> selectAwaitCompletion(select: SelectInstance<R>, block: suspend (T) -> R) {
+ val state = this.state
+ // Note: await is non-atomic (can be cancelled while dispatched)
+ if (state is CompletedExceptionally)
+ select.resumeSelectCancellableWithException(state.cause)
+ else
+ block.startCoroutineCancellable(state.unboxState() as T, select.completion)
+ }
+}
+
+/*
+ * Class to represent object as the final state of the Job
+ */
+private class IncompleteStateBox(@JvmField val state: Incomplete)
+internal fun Any?.boxIncomplete(): Any? = if (this is Incomplete) IncompleteStateBox(this) else this
+internal fun Any?.unboxState(): Any? = (this as? IncompleteStateBox)?.state ?: this
+
+// --------------- helper classes & constants for job implementation
+
+private const val COMPLETING_ALREADY_COMPLETING = 0
+private const val COMPLETING_COMPLETED = 1
+private const val COMPLETING_WAITING_CHILDREN = 2
+private const val COMPLETING_RETRY = 3
+
+private const val RETRY = -1
+private const val FALSE = 0
+private const val TRUE = 1
+
+@SharedImmutable
+private val SEALED = Symbol("SEALED")
+@SharedImmutable
+private val EMPTY_NEW = Empty(false)
+@SharedImmutable
+private val EMPTY_ACTIVE = Empty(true)
+
+private class Empty(override val isActive: Boolean) : Incomplete {
+ override val list: NodeList? get() = null
+ override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}"
+}
+
+internal open class JobImpl(parent: Job?) : JobSupport(true), CompletableJob {
+ init { initParentJobInternal(parent) }
+ override val onCancelComplete get() = true
+ /*
+ * Check whether parent is able to handle exceptions as well.
+ * With this check, an exception in that pattern will be handled once:
+ * ```
+ * launch {
+ * val child = Job(coroutineContext[Job])
+ * launch(child) { throw ... }
+ * }
+ * ```
+ */
+ override val handlesException: Boolean = handlesException()
+ override fun complete() = makeCompleting(Unit)
+ override fun completeExceptionally(exception: Throwable): Boolean =
+ makeCompleting(CompletedExceptionally(exception))
+
+ @JsName("handlesExceptionF")
+ private fun handlesException(): Boolean {
+ var parentJob = (parentHandle as? ChildHandleNode)?.job ?: return false
+ while (true) {
+ if (parentJob.handlesException) return true
+ parentJob = (parentJob.parentHandle as? ChildHandleNode)?.job ?: return false
+ }
+ }
+}
+
+// -------- invokeOnCompletion nodes
+
+internal interface Incomplete {
+ val isActive: Boolean
+ 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 {
+ override val isActive: Boolean get() = true
+ override val list: NodeList? get() = null
+ override fun dispose() = (job as JobSupport).removeNode(this)
+}
+
+internal class NodeList : LockFreeLinkedListHead(), Incomplete {
+ override val isActive: Boolean get() = true
+ override val list: NodeList get() = this
+
+ fun getString(state: String) = buildString {
+ append("List{")
+ append(state)
+ append("}[")
+ var first = true
+ this@NodeList.forEach<JobNode<*>> { node ->
+ if (first) first = false else append(", ")
+ append(node)
+ }
+ append("]")
+ }
+
+ override fun toString(): String =
+ if (DEBUG) getString("Active") else super.toString()
+}
+
+internal class InactiveNodeList(
+ override val list: NodeList
+) : Incomplete {
+ override val isActive: Boolean get() = false
+ override fun toString(): String = if (DEBUG) list.getString("New") else super.toString()
+}
+
+private class InvokeOnCompletion(
+ job: Job,
+ private val handler: CompletionHandler
+) : JobNode<Job>(job) {
+ 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) {
+ 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) {
+ override fun invoke(cause: Throwable?) {
+ val state = job.state
+ assert { state !is Incomplete }
+ if (state is CompletedExceptionally) {
+ // Resume with exception in atomic way to preserve exception
+ continuation.resumeWithExceptionMode(state.cause, MODE_ATOMIC_DEFAULT)
+ } else {
+ // Resuming with value in a cancellable way (AwaitContinuation is configured for this mode).
+ @Suppress("UNCHECKED_CAST")
+ continuation.resume(state.unboxState() as T)
+ }
+ }
+ override fun toString() = "ResumeAwaitOnCompletion[$continuation]"
+}
+
+internal class DisposeOnCompletion(
+ job: Job,
+ private val handle: DisposableHandle
+) : JobNode<Job>(job) {
+ 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) {
+ override fun invoke(cause: Throwable?) {
+ if (select.trySelect(null))
+ 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) {
+ override fun invoke(cause: Throwable?) {
+ if (select.trySelect(null))
+ job.selectAwaitCompletion(select, block)
+ }
+ override fun toString(): String = "SelectAwaitOnCompletion[$select]"
+}
+
+// -------- invokeOnCancellation nodes
+
+/**
+ * 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)
+
+private class InvokeOnCancelling(
+ job: Job,
+ private val handler: CompletionHandler
+) : JobCancellingNode<Job>(job) {
+ // 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 {
+ 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) {
+ override fun invoke(cause: Throwable?) {
+ child.cancel(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
new file mode 100644
index 00000000..2a20095a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * Base class for special [CoroutineDispatcher] which is confined to application "Main" or "UI" thread
+ * and used for any UI-based activities. Instance of `MainDispatcher` can be obtained by [Dispatchers.Main].
+ *
+ * Platform may or may not provide instance of `MainDispatcher`, see documentation to [Dispatchers.Main]
+ */
+public abstract class MainCoroutineDispatcher : CoroutineDispatcher() {
+
+ /**
+ * Returns dispatcher that executes coroutines immediately when it is already in the right context
+ * (e.g. current looper is the same as this handler's looper) without an additional [re-dispatch][CoroutineDispatcher.dispatch].
+ *
+ * Immediate dispatcher is safe from stack overflows and in case of nested invocations forms event-loop similar to [Dispatchers.Unconfined].
+ * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation.
+ *
+ * Example of usage:
+ * ```
+ * suspend fun updateUiElement(val text: String) {
+ * /*
+ * * If it is known that updateUiElement can be invoked both from the Main thread and from other threads,
+ * * `immediate` dispatcher is used as a performance optimization to avoid unnecessary dispatch.
+ * *
+ * * In that case, when `updateUiElement` is invoked from the Main thread, `uiElement.text` will be
+ * * invoked immediately without any dispatching, otherwise, the `Dispatchers.Main` dispatch cycle will be triggered.
+ * */
+ * withContext(Dispatchers.Main.immediate) {
+ * uiElement.text = text
+ * }
+ * // Do context-independent logic such as logging
+ * }
+ * ```
+ *
+ * Method may throw [UnsupportedOperationException] if immediate dispatching is not supported by current dispatcher,
+ * please refer to specific dispatcher documentation.
+ *
+ * [Dispatchers.Main] supports immediate execution for Android, JavaFx and Swing platforms.
+ */
+ public abstract val immediate: MainCoroutineDispatcher
+}
diff --git a/kotlinx-coroutines-core/common/src/NonCancellable.kt b/kotlinx-coroutines-core/common/src/NonCancellable.kt
new file mode 100644
index 00000000..c48faea7
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/NonCancellable.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("DEPRECATION_ERROR")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+
+/**
+ * A non-cancelable job that is always [active][Job.isActive]. It is designed for [withContext] function
+ * to prevent cancellation of code blocks that need to be executed without cancellation.
+ *
+ * Use it like this:
+ * ```
+ * withContext(NonCancellable) {
+ * // this code will not be cancelled
+ * }
+ * ```
+ */
+public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
+ /**
+ * Always returns `true`.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override val isActive: Boolean get() = true
+
+ /**
+ * Always returns `false`.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override val isCompleted: Boolean get() = false
+
+ /**
+ * Always returns `false`.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override val isCancelled: Boolean get() = false
+
+ /**
+ * Always returns `false`.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override fun start(): Boolean = false
+
+ /**
+ * Always throws [UnsupportedOperationException].
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override suspend fun join() {
+ throw UnsupportedOperationException("This job is always active")
+ }
+
+ /**
+ * Always throws [UnsupportedOperationException].
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ override val onJoin: SelectClause0
+ get() = throw UnsupportedOperationException("This job is always active")
+
+ /**
+ * Always throws [IllegalStateException].
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ 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
+ override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle =
+ NonDisposableHandle
+
+ /**
+ * Always returns no-op handle.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override fun invokeOnCompletion(onCancelling: Boolean, invokeImmediately: Boolean, handler: CompletionHandler): DisposableHandle =
+ NonDisposableHandle
+
+ /**
+ * Does nothing.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override fun cancel(cause: CancellationException?) {}
+
+ /**
+ * Always returns `false`.
+ * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel].
+ */
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ override fun cancel(cause: Throwable?): Boolean = false // never handles exceptions
+
+ /**
+ * Always returns [emptySequence].
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override val children: Sequence<Job>
+ get() = emptySequence()
+
+ /**
+ * Always returns [NonDisposableHandle] and does not do anything.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+ @InternalCoroutinesApi
+ override fun attachChild(child: ChildJob): ChildHandle = NonDisposableHandle
+
+ /** @suppress */
+ override fun toString(): String {
+ return "NonCancellable"
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/ResumeMode.kt b/kotlinx-coroutines-core/common/src/ResumeMode.kt
new file mode 100644
index 00000000..0afea98c
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/ResumeMode.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+@PublishedApi internal const val MODE_ATOMIC_DEFAULT = 0 // schedule non-cancellable dispatch for suspendCoroutine
+@PublishedApi internal const val MODE_CANCELLABLE = 1 // schedule cancellable dispatch for suspendCancellableCoroutine
+@PublishedApi internal const val MODE_DIRECT = 2 // when the context is right just invoke the delegate continuation direct
+@PublishedApi internal const val MODE_UNDISPATCHED = 3 // when the thread is right, but need to mark it with current coroutine
+@PublishedApi internal const val MODE_IGNORE = 4 // don't do anything
+
+internal val Int.isCancellableMode get() = this == MODE_CANCELLABLE
+internal val Int.isDispatchedMode get() = this == MODE_ATOMIC_DEFAULT || this == MODE_CANCELLABLE
+
+internal fun <T> Continuation<T>.resumeMode(value: T, mode: Int) {
+ when (mode) {
+ MODE_ATOMIC_DEFAULT -> resume(value)
+ MODE_CANCELLABLE -> resumeCancellable(value)
+ MODE_DIRECT -> resumeDirect(value)
+ MODE_UNDISPATCHED -> (this as DispatchedContinuation).resumeUndispatched(value)
+ MODE_IGNORE -> {}
+ else -> error("Invalid mode $mode")
+ }
+}
+
+internal fun <T> Continuation<T>.resumeWithExceptionMode(exception: Throwable, mode: Int) {
+ when (mode) {
+ MODE_ATOMIC_DEFAULT -> resumeWithException(exception)
+ MODE_CANCELLABLE -> resumeCancellableWithException(exception)
+ MODE_DIRECT -> resumeDirectWithException(exception)
+ MODE_UNDISPATCHED -> (this as DispatchedContinuation).resumeUndispatchedWithException(exception)
+ MODE_IGNORE -> {}
+ else -> error("Invalid mode $mode")
+ }
+}
+
+internal fun <T> Continuation<T>.resumeUninterceptedMode(value: T, mode: Int) {
+ when (mode) {
+ MODE_ATOMIC_DEFAULT -> intercepted().resume(value)
+ MODE_CANCELLABLE -> intercepted().resumeCancellable(value)
+ MODE_DIRECT -> resume(value)
+ MODE_UNDISPATCHED -> withCoroutineContext(context, null) { resume(value) }
+ MODE_IGNORE -> {}
+ else -> error("Invalid mode $mode")
+ }
+}
+
+internal fun <T> Continuation<T>.resumeUninterceptedWithExceptionMode(exception: Throwable, mode: Int) {
+ when (mode) {
+ MODE_ATOMIC_DEFAULT -> intercepted().resumeWithException(exception)
+ MODE_CANCELLABLE -> intercepted().resumeCancellableWithException(exception)
+ MODE_DIRECT -> resumeWithException(exception)
+ MODE_UNDISPATCHED -> withCoroutineContext(context, null) { resumeWithException(exception) }
+ MODE_IGNORE -> {}
+ else -> error("Invalid mode $mode")
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/Runnable.common.kt b/kotlinx-coroutines-core/common/src/Runnable.common.kt
new file mode 100644
index 00000000..6c258d85
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Runnable.common.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * A runnable task for [CoroutineDispatcher.dispatch].
+ */
+public expect interface Runnable {
+ /**
+ * @suppress
+ */
+ public fun run()
+}
+
+/**
+ * Creates [Runnable] task instance.
+ */
+@Suppress("FunctionName")
+public expect inline fun Runnable(crossinline block: () -> Unit): Runnable \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/SchedulerTask.common.kt b/kotlinx-coroutines-core/common/src/SchedulerTask.common.kt
new file mode 100644
index 00000000..7b767f51
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/SchedulerTask.common.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+internal expect abstract class SchedulerTask() : Runnable
+
+internal expect interface SchedulerTaskContext
+
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+internal expect val SchedulerTask.taskContext: SchedulerTaskContext
+
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+internal expect inline fun SchedulerTaskContext.afterTask()
diff --git a/kotlinx-coroutines-core/common/src/Supervisor.kt b/kotlinx-coroutines-core/common/src/Supervisor.kt
new file mode 100644
index 00000000..63542a9a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Supervisor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DEPRECATION_ERROR")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.jvm.*
+
+/**
+ * Creates a _supervisor_ job object in an active state.
+ * Children of a supervisor job can fail independently of each other.
+ *
+ * A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children,
+ * so a supervisor can implement a custom policy for handling failures of its children:
+ *
+ * * A failure of a child job that was created using [launch][CoroutineScope.launch] can be handled via [CoroutineExceptionHandler] in the context.
+ * * A failure of a child job that was created using [async][CoroutineScope.async] can be handled via [Deferred.await] on the resulting deferred value.
+ *
+ * If [parent] job is specified, then this supervisor job becomes a child job of its parent and is cancelled when its
+ * parent fails or is cancelled. All this supervisor's children are cancelled in this case, too. The invocation of
+ * [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
+ *
+ * @param parent an optional parent job.
+ */
+@Suppress("FunctionName")
+public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)
+
+/** @suppress Binary compatibility only */
+@Suppress("FunctionName")
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+@JvmName("SupervisorJob")
+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].
+ *
+ * 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,
+ * but does not cancel parent job.
+ */
+public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R =
+ suspendCoroutineUninterceptedOrReturn { uCont ->
+ val coroutine = SupervisorCoroutine(uCont.context, uCont)
+ coroutine.startUndispatchedOrReturn(coroutine, block)
+ }
+
+private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
+ override fun childCancelled(cause: Throwable): Boolean = false
+}
+
+private class SupervisorCoroutine<in T>(
+ context: CoroutineContext,
+ uCont: Continuation<T>
+) : ScopeCoroutine<T>(context, uCont) {
+ override fun childCancelled(cause: Throwable): Boolean = false
+}
diff --git a/kotlinx-coroutines-core/common/src/Timeout.kt b/kotlinx-coroutines-core/common/src/Timeout.kt
new file mode 100644
index 00000000..8bfaf336
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Timeout.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016-2018 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.intrinsics.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.jvm.*
+
+/**
+ * Runs a given suspending [block] of code inside a coroutine with a specified [timeout][timeMillis] and throws
+ * a [TimeoutCancellationException] if the timeout was exceeded.
+ *
+ * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
+ * the cancellable suspending function inside the block throws a [TimeoutCancellationException].
+ *
+ * The sibling function that does not throw an exception on timeout is [withTimeoutOrNull].
+ * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
+ *
+ * Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
+ *
+ * @param timeMillis timeout time in milliseconds.
+ */
+public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T {
+ if (timeMillis <= 0L) throw TimeoutCancellationException("Timed out immediately")
+ return suspendCoroutineUninterceptedOrReturn { uCont ->
+ setupTimeout(TimeoutCoroutine(timeMillis, uCont), block)
+ }
+}
+
+/**
+ * Runs a given suspending block of code inside a coroutine with a specified [timeout][timeMillis] and returns
+ * `null` if this timeout was exceeded.
+ *
+ * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
+ * cancellable suspending function inside the block throws a [TimeoutCancellationException].
+ *
+ * The sibling function that throws an exception on timeout is [withTimeout].
+ * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
+ *
+ * Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
+ *
+ * @param timeMillis timeout time in milliseconds.
+ */
+public suspend fun <T> withTimeoutOrNull(timeMillis: Long, block: suspend CoroutineScope.() -> T): T? {
+ if (timeMillis <= 0L) return null
+
+ var coroutine: TimeoutCoroutine<T?, T?>? = null
+ try {
+ return suspendCoroutineUninterceptedOrReturn { uCont ->
+ val timeoutCoroutine = TimeoutCoroutine(timeMillis, uCont)
+ coroutine = timeoutCoroutine
+ setupTimeout<T?, T?>(timeoutCoroutine, block)
+ }
+ } catch (e: TimeoutCancellationException) {
+ // Return null if it's our exception, otherwise propagate it upstream (e.g. in case of nested withTimeouts)
+ if (e.coroutine === coroutine) {
+ return null
+ }
+ throw e
+ }
+}
+
+private fun <U, T: U> setupTimeout(
+ coroutine: TimeoutCoroutine<U, T>,
+ block: suspend CoroutineScope.() -> T
+): Any? {
+ // schedule cancellation of this coroutine on time
+ val cont = coroutine.uCont
+ val context = cont.context
+ coroutine.disposeOnCompletion(context.delay.invokeOnTimeout(coroutine.time, coroutine))
+ // restart the block using a new coroutine with a new job,
+ // however, start it undispatched, because we already are in the proper context
+ return coroutine.startUndispatchedOrReturnIgnoreTimeout(coroutine, block)
+}
+
+private open class TimeoutCoroutine<U, in T: U>(
+ @JvmField val time: Long,
+ @JvmField val uCont: Continuation<U> // unintercepted continuation
+) : AbstractCoroutine<T>(uCont.context, active = true), Runnable, Continuation<T>, CoroutineStackFrame {
+ override val defaultResumeMode: Int get() = MODE_DIRECT
+ override val callerFrame: CoroutineStackFrame? get() = (uCont as? CoroutineStackFrame)
+ override fun getStackTraceElement(): StackTraceElement? = null
+ override val isScopedCoroutine: Boolean get() = true
+
+ @Suppress("LeakingThis", "Deprecation")
+ override fun run() {
+ cancelCoroutine(TimeoutCancellationException(time, this))
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun afterCompletionInternal(state: Any?, mode: Int) {
+ if (state is CompletedExceptionally)
+ uCont.resumeUninterceptedWithExceptionMode(state.cause, mode)
+ else
+ uCont.resumeUninterceptedMode(state as T, mode)
+ }
+
+ override fun nameString(): String =
+ "${super.nameString()}(timeMillis=$time)"
+}
+
+/**
+ * This exception is thrown by [withTimeout] to indicate timeout.
+ */
+public class TimeoutCancellationException internal constructor(
+ message: String,
+ @JvmField internal val coroutine: Job?
+) : CancellationException(message) {
+ /**
+ * Creates a timeout exception with the given message.
+ * This constructor is needed for exception stack-traces recovery.
+ */
+ @Suppress("UNUSED")
+ internal constructor(message: String) : this(message, null)
+}
+
+@Suppress("FunctionName")
+internal fun TimeoutCancellationException(
+ time: Long,
+ coroutine: Job
+) : TimeoutCancellationException = TimeoutCancellationException("Timed out waiting for $time ms", coroutine)
+
+
diff --git a/kotlinx-coroutines-core/common/src/Unconfined.kt b/kotlinx-coroutines-core/common/src/Unconfined.kt
new file mode 100644
index 00000000..83e27a55
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Unconfined.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+/**
+ * A coroutine dispatcher that is not confined to any specific thread.
+ */
+internal object Unconfined : CoroutineDispatcher() {
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
+ override fun dispatch(context: CoroutineContext, block: Runnable) { throw UnsupportedOperationException() }
+ override fun toString(): String = "Unconfined"
+}
diff --git a/kotlinx-coroutines-core/common/src/Yield.kt b/kotlinx-coroutines-core/common/src/Yield.kt
new file mode 100644
index 00000000..78ab27fb
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/Yield.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+/**
+ * Yields a thread (or thread pool) of the current coroutine dispatcher to other coroutines to run.
+ * If the coroutine dispatcher does not have its own thread pool (like [Dispatchers.Unconfined]) then this
+ * function does nothing, but checks if the coroutine [Job] was completed.
+ * This suspending function is cancellable.
+ * If the [Job] of the current coroutine is cancelled or completed when this suspending function is invoked or while
+ * this function is waiting for dispatching, it resumes with [CancellationException].
+ */
+public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
+ val context = uCont.context
+ context.checkCompletion()
+ val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit
+ if (!cont.dispatcher.isDispatchNeeded(context)) {
+ return@sc if (cont.yieldUndispatched()) COROUTINE_SUSPENDED else Unit
+ }
+ cont.dispatchYield(Unit)
+ 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
new file mode 100644
index 00000000..7b8f96b6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
@@ -0,0 +1,1071 @@
+/*
+ * 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 kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Abstract send channel. It is a base class for all send channel implementations.
+ */
+internal abstract class AbstractSendChannel<E> : SendChannel<E> {
+ /** @suppress **This is unstable API and it is subject to change.** */
+ protected val queue = LockFreeLinkedListHead()
+
+ // ------ extension points for buffered channels ------
+
+ /**
+ * Returns `true` if [isBufferFull] is always `true`.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected abstract val isBufferAlwaysFull: Boolean
+
+ /**
+ * Returns `true` if this channel's buffer is full.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected abstract val isBufferFull: Boolean
+
+ // State transitions: null -> handler -> HANDLER_INVOKED
+ private val onCloseHandler = atomic<Any?>(null)
+
+ // ------ internal functions for override by buffered channels ------
+
+ /**
+ * Tries to add element to buffer or to queued receiver.
+ * Return type is `OFFER_SUCCESS | OFFER_FAILED | Closed`.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun offerInternal(element: E): Any {
+ while (true) {
+ val receive = takeFirstReceiveOrPeekClosed() ?: return OFFER_FAILED
+ val token = receive.tryResumeReceive(element, idempotent = null)
+ if (token != null) {
+ receive.completeResumeReceive(token)
+ return receive.offerResult
+ }
+ }
+ }
+
+ /**
+ * Tries to add element to buffer or to queued receiver if select statement clause was not selected yet.
+ * Return type is `ALREADY_SELECTED | OFFER_SUCCESS | OFFER_FAILED | Closed`.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun offerSelectInternal(element: E, select: SelectInstance<*>): Any {
+ // offer atomically with select
+ val offerOp = describeTryOffer(element)
+ val failure = select.performAtomicTrySelect(offerOp)
+ if (failure != null) return failure
+ val receive = offerOp.result
+ receive.completeResumeReceive(offerOp.resumeToken!!)
+ return receive.offerResult
+ }
+
+ // ------ state functions & helpers for concrete implementations ------
+
+ /**
+ * Returns non-null closed token if it is last in the queue.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected val closedForSend: Closed<*>? get() = (queue.prevNode as? Closed<*>)?.also { helpClose(it) }
+
+ /**
+ * Returns non-null closed token if it is first in the queue.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected val closedForReceive: Closed<*>? get() = (queue.nextNode as? Closed<*>)?.also { helpClose(it) }
+
+ /**
+ * Retrieves first sending waiter from the queue or returns closed token.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected fun takeFirstSendOrPeekClosed(): Send? =
+ queue.removeFirstIfIsInstanceOfOrPeekIf<Send> { it is Closed<*> }
+
+ /**
+ * Queues buffered element, returns null on success or
+ * returns node reference if it was already closed or is waiting for receive.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected fun sendBuffered(element: E): ReceiveOrClosed<*>? {
+ queue.addLastIfPrev(SendBuffered(element)) { prev ->
+ if (prev is ReceiveOrClosed<*>) return@sendBuffered prev
+ true
+ }
+ return null
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected fun describeSendBuffered(element: E): AddLastDesc<*> = SendBufferedDesc(queue, element)
+
+ private open class SendBufferedDesc<E>(
+ queue: LockFreeLinkedListHead,
+ element: E
+ ) : AddLastDesc<SendBuffered<E>>(queue, SendBuffered(element)) {
+ override fun failure(affected: LockFreeLinkedListNode): Any? = when (affected) {
+ is Closed<*> -> affected
+ is ReceiveOrClosed<*> -> OFFER_FAILED
+ else -> null
+ }
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected fun describeSendConflated(element: E): AddLastDesc<*> = SendConflatedDesc(queue, element)
+
+ private class SendConflatedDesc<E>(
+ queue: LockFreeLinkedListHead,
+ element: E
+ ) : SendBufferedDesc<E>(queue, element) {
+ override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) {
+ super.finishOnSuccess(affected, next)
+ // remove previous SendBuffered
+ (affected as? SendBuffered<*>)?.remove()
+ }
+ }
+
+ // ------ SendChannel ------
+
+ public final override val isClosedForSend: Boolean get() = closedForSend != null
+ public final override val isFull: Boolean get() = full
+ private val full: Boolean get() = queue.nextNode !is ReceiveOrClosed<*> && isBufferFull // TODO rename to `isFull`
+
+ public final override suspend fun send(element: E) {
+ // fast path -- try offer non-blocking
+ if (offer(element)) return
+ // slow-path does suspend
+ return sendSuspend(element)
+ }
+
+ internal suspend fun sendFair(element: E) {
+ if (offer(element)) {
+ yield() // Works only on fast path to properly work in sequential use-cases
+ return
+ }
+ return sendSuspend(element)
+ }
+
+ public final override fun offer(element: E): Boolean {
+ val result = offerInternal(element)
+ return when {
+ result === OFFER_SUCCESS -> true
+ // We should check for closed token on offer as well, otherwise offer won't be linearizable
+ // in the face of concurrent close()
+ result === OFFER_FAILED -> throw closedForSend?.sendException?.let { recoverStackTrace(it) } ?: return false
+ result is Closed<*> -> throw recoverStackTrace(result.sendException)
+ else -> error("offerInternal returned $result")
+ }
+ }
+
+ private suspend fun sendSuspend(element: E): Unit = suspendAtomicCancellableCoroutine sc@ { cont ->
+ loop@ while (true) {
+ if (full) {
+ val send = SendElement(element, cont)
+ val enqueueResult = enqueueSend(send)
+ when {
+ enqueueResult == null -> { // enqueued successfully
+ cont.removeOnCancellation(send)
+ return@sc
+ }
+ enqueueResult is Closed<*> -> {
+ helpClose(enqueueResult)
+ cont.resumeWithException(enqueueResult.sendException)
+ return@sc
+ }
+ enqueueResult === ENQUEUE_FAILED -> {} // try to offer instead
+ enqueueResult is Receive<*> -> {} // try to offer instead
+ else -> error("enqueueSend returned $enqueueResult")
+ }
+ }
+ // hm... receiver is waiting or buffer is not full. try to offer
+ val offerResult = offerInternal(element)
+ when {
+ offerResult === OFFER_SUCCESS -> {
+ cont.resume(Unit)
+ return@sc
+ }
+ offerResult === OFFER_FAILED -> continue@loop
+ offerResult is Closed<*> -> {
+ helpClose(offerResult)
+ cont.resumeWithException(offerResult.sendException)
+ return@sc
+ }
+ else -> error("offerInternal returned $offerResult")
+ }
+ }
+ }
+
+ /**
+ * Result is:
+ * * null -- successfully enqueued
+ * * ENQUEUE_FAILED -- buffer is not full (should not enqueue)
+ * * ReceiveOrClosed<*> -- receiver is waiting or it is closed (should not enqueue)
+ */
+ private fun enqueueSend(send: Send): Any? {
+ if (isBufferAlwaysFull) {
+ queue.addLastIfPrev(send) { prev ->
+ if (prev is ReceiveOrClosed<*>) return@enqueueSend prev
+ true
+ }
+ } else {
+ if (!queue.addLastIfPrevAndIf(send, { prev ->
+ if (prev is ReceiveOrClosed<*>) return@enqueueSend prev
+ true
+ }, { isBufferFull }))
+ return ENQUEUE_FAILED
+ }
+ return null
+ }
+
+ public override fun close(cause: Throwable?): Boolean {
+ val closed = Closed<E>(cause)
+
+ /*
+ * Try to commit close by adding a close token to the end of the queue.
+ * Successful -> we're now responsible for closing receivers
+ * Not successful -> help closing pending receivers to maintain invariant
+ * "if (!close()) next send will throw"
+ */
+ val closeAdded = queue.addLastIfPrev(closed, { it !is Closed<*> })
+ if (!closeAdded) {
+ val actualClosed = queue.prevNode as Closed<*>
+ helpClose(actualClosed)
+ return false
+ }
+
+ helpClose(closed)
+ invokeOnCloseHandler(cause)
+ return true
+ }
+
+ private fun invokeOnCloseHandler(cause: Throwable?) {
+ val handler = onCloseHandler.value
+ if (handler !== null && handler !== HANDLER_INVOKED
+ && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) {
+ // CAS failed -> concurrent invokeOnClose() invoked handler
+ @Suppress("UNCHECKED_CAST")
+ (handler as Handler)(cause)
+ }
+ }
+
+ override fun invokeOnClose(handler: Handler) {
+ // Intricate dance for concurrent invokeOnClose and close calls
+ if (!onCloseHandler.compareAndSet(null, handler)) {
+ val value = onCloseHandler.value
+ if (value === HANDLER_INVOKED) {
+ throw IllegalStateException("Another handler was already registered and successfully invoked")
+ }
+
+ throw IllegalStateException("Another handler was already registered: $value")
+ } else {
+ val closedToken = closedForSend
+ if (closedToken != null && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) {
+ // CAS failed -> close() call invoked handler
+ (handler)(closedToken.closeCause)
+ }
+ }
+ }
+
+ private fun helpClose(closed: Closed<*>) {
+ /*
+ * It's important to traverse list from right to left to avoid races with sender.
+ * Consider channel state
+ * head sentinel -> [receiver 1] -> [receiver 2] -> head sentinel
+ * T1 invokes receive()
+ * T2 invokes close()
+ * T3 invokes close() + send(value)
+ *
+ * If both will traverse list from left to right, following non-linearizable history is possible:
+ * [close -> false], [send -> transferred 'value' to receiver]
+ */
+ while (true) {
+ val previous = closed.prevNode
+ // Channel is empty or has no receivers
+ if (previous is LockFreeLinkedListHead || previous !is Receive<*>) {
+ break
+ }
+
+ if (!previous.remove()) {
+ // failed to remove the node (due to race) -- retry finding non-removed prevNode
+ // NOTE: remove() DOES NOT help pending remove operation (that marked next pointer)
+ previous.helpRemove() // make sure remove is complete before continuing
+ continue
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ previous as Receive<E> // type assertion
+ previous.resumeReceiveClosed(closed)
+ }
+ onClosedIdempotent(closed)
+ }
+
+ /**
+ * Invoked when channel is closed as the last action of [close] invocation.
+ * This method should be idempotent and can be called multiple times.
+ */
+ protected open fun onClosedIdempotent(closed: LockFreeLinkedListNode) {}
+
+ /**
+ * Retrieves first receiving waiter from the queue or returns closed token.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun takeFirstReceiveOrPeekClosed(): ReceiveOrClosed<E>? =
+ queue.removeFirstIfIsInstanceOfOrPeekIf<ReceiveOrClosed<E>>({ it is Closed<*> })
+
+ // ------ registerSelectSend ------
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected fun describeTryOffer(element: E): TryOfferDesc<E> = TryOfferDesc(element, queue)
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected class TryOfferDesc<E>(
+ @JvmField val element: E,
+ queue: LockFreeLinkedListHead
+ ) : RemoveFirstDesc<ReceiveOrClosed<E>>(queue) {
+ @JvmField var resumeToken: Any? = null
+
+ override fun failure(affected: LockFreeLinkedListNode): Any? = when (affected) {
+ is Closed<*> -> affected
+ !is ReceiveOrClosed<*> -> OFFER_FAILED
+ else -> null
+ }
+
+ override fun validatePrepared(node: ReceiveOrClosed<E>): Boolean {
+ val token = node.tryResumeReceive(element, idempotent = this) ?: return false
+ resumeToken = token
+ return true
+ }
+ }
+
+ final override val onSend: SelectClause2<E, SendChannel<E>>
+ get() = object : SelectClause2<E, SendChannel<E>> {
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, param: E, block: suspend (SendChannel<E>) -> R) {
+ registerSelectSend(select, param, block)
+ }
+ }
+
+ private fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend (SendChannel<E>) -> R) {
+ while (true) {
+ if (select.isSelected) return
+ if (full) {
+ val node = SendSelect(element, this, select, block)
+ val enqueueResult = enqueueSend(node)
+ when {
+ enqueueResult == null -> { // enqueued successfully
+ select.disposeOnSelect(node)
+ return
+ }
+ enqueueResult is Closed<*> -> {
+ helpClose(enqueueResult)
+ throw recoverStackTrace(enqueueResult.sendException)
+ }
+ enqueueResult === ENQUEUE_FAILED -> {} // try to offer
+ enqueueResult is Receive<*> -> {} // try to offer
+ else -> error("enqueueSend returned $enqueueResult ")
+ }
+ }
+ // hm... receiver is waiting or buffer is not full. try to offer
+ val offerResult = offerSelectInternal(element, select)
+ when {
+ offerResult === ALREADY_SELECTED -> return
+ offerResult === OFFER_FAILED -> {} // retry
+ offerResult === OFFER_SUCCESS -> {
+ block.startCoroutineUnintercepted(receiver = this, completion = select.completion)
+ return
+ }
+ offerResult is Closed<*> -> {
+ helpClose(offerResult)
+ throw recoverStackTrace(offerResult.sendException)
+ }
+ else -> error("offerSelectInternal returned $offerResult")
+ }
+ }
+ }
+
+ // ------ debug ------
+
+ public override fun toString() =
+ "$classSimpleName@$hexAddress{$queueDebugStateString}$bufferDebugString"
+
+ private val queueDebugStateString: String
+ get() {
+ val head = queue.nextNode
+ if (head === queue) return "EmptyQueue"
+ var result = when (head) {
+ is Closed<*> -> head.toString()
+ is Receive<*> -> "ReceiveQueued"
+ is Send -> "SendQueued"
+ else -> "UNEXPECTED:$head" // should not happen
+ }
+ val tail = queue.prevNode
+ if (tail !== head) {
+ result += ",queueSize=${countQueueSize()}"
+ if (tail is Closed<*>) result += ",closedForSend=$tail"
+ }
+ return result
+ }
+
+ private fun countQueueSize(): Int {
+ var size = 0
+ queue.forEach<LockFreeLinkedListNode> { size++ }
+ return size
+ }
+
+ protected open val bufferDebugString: String get() = ""
+
+ // ------ private ------
+
+ private class SendSelect<E, R>(
+ override val pollResult: Any?,
+ @JvmField val channel: SendChannel<E>,
+ @JvmField val select: SelectInstance<R>,
+ @JvmField val block: suspend (SendChannel<E>) -> R
+ ) : Send(), DisposableHandle {
+ override fun tryResumeSend(idempotent: Any?): Any? =
+ if (select.trySelect(idempotent)) SELECT_STARTED else null
+
+ override fun completeResumeSend(token: Any) {
+ assert { token === SELECT_STARTED }
+ block.startCoroutine(receiver = channel, completion = select.completion)
+ }
+
+ override fun dispose() { // invoked on select completion
+ remove()
+ }
+
+ override fun resumeSendClosed(closed: Closed<*>) {
+ if (select.trySelect(null))
+ select.resumeSelectCancellableWithException(closed.sendException)
+ }
+
+ override fun toString(): String = "SendSelect($pollResult)[$channel, $select]"
+ }
+
+ internal class SendBuffered<out E>(
+ @JvmField val element: E
+ ) : Send() {
+ override val pollResult: Any? get() = element
+ override fun tryResumeSend(idempotent: Any?): Any? = SEND_RESUMED
+ override fun completeResumeSend(token: Any) { assert { token === SEND_RESUMED } }
+ override fun resumeSendClosed(closed: Closed<*>) {}
+ }
+}
+
+/**
+ * Abstract send/receive channel. It is a base class for all channel implementations.
+ */
+internal abstract class AbstractChannel<E> : AbstractSendChannel<E>(), Channel<E> {
+ // ------ extension points for buffered channels ------
+
+ /**
+ * Returns `true` if [isBufferEmpty] is always `true`.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected abstract val isBufferAlwaysEmpty: Boolean
+
+ /**
+ * Returns `true` if this channel's buffer is empty.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected abstract val isBufferEmpty: Boolean
+
+ // ------ internal functions for override by buffered channels ------
+
+ /**
+ * Tries to remove element from buffer or from queued sender.
+ * Return type is `E | POLL_FAILED | Closed`
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun pollInternal(): Any? {
+ while (true) {
+ val send = takeFirstSendOrPeekClosed() ?: return POLL_FAILED
+ val token = send.tryResumeSend(idempotent = null)
+ if (token != null) {
+ send.completeResumeSend(token)
+ return send.pollResult
+ }
+ }
+ }
+
+ /**
+ * Tries to remove element from buffer or from queued sender if select statement clause was not selected yet.
+ * Return type is `ALREADY_SELECTED | E | POLL_FAILED | Closed`
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun pollSelectInternal(select: SelectInstance<*>): Any? {
+ // poll atomically with select
+ val pollOp = describeTryPoll()
+ val failure = select.performAtomicTrySelect(pollOp)
+ if (failure != null) return failure
+ val send = pollOp.result
+ send.completeResumeSend(pollOp.resumeToken!!)
+ return pollOp.pollResult
+ }
+
+ // ------ state functions & helpers for concrete implementations ------
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected val hasReceiveOrClosed: Boolean get() = queue.nextNode is ReceiveOrClosed<*>
+
+ // ------ ReceiveChannel ------
+
+ public final override val isClosedForReceive: Boolean get() = closedForReceive != null && isBufferEmpty
+ public final override val isEmpty: Boolean get() = queue.nextNode !is Send && isBufferEmpty
+
+ public final override suspend fun receive(): E {
+ // fast path -- try poll non-blocking
+ val result = pollInternal()
+ if (result !== POLL_FAILED) return receiveResult(result)
+ // slow-path does suspend
+ return receiveSuspend(RECEIVE_THROWS_ON_CLOSE)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun receiveResult(result: Any?): E {
+ if (result is Closed<*>) throw recoverStackTrace(result.receiveException)
+ return result as E
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private suspend fun <R> receiveSuspend(receiveMode: Int): R = suspendAtomicCancellableCoroutine sc@ { cont ->
+ val receive = ReceiveElement<E>(cont as CancellableContinuation<Any?>, receiveMode)
+ while (true) {
+ if (enqueueReceive(receive)) {
+ removeReceiveOnCancel(cont, receive)
+ return@sc
+ }
+ // hm... something is not right. try to poll
+ val result = pollInternal()
+ if (result is Closed<*>) {
+ receive.resumeReceiveClosed(result)
+ return@sc
+ }
+ if (result !== POLL_FAILED) {
+ cont.resume(receive.resumeValue(result as E))
+ return@sc
+ }
+ }
+ }
+
+ private fun enqueueReceive(receive: Receive<E>): Boolean {
+ val result = if (isBufferAlwaysEmpty)
+ queue.addLastIfPrev(receive) { it !is Send } else
+ queue.addLastIfPrevAndIf(receive, { it !is Send }, { isBufferEmpty })
+ if (result) onReceiveEnqueued()
+ return result
+ }
+
+ public final override suspend fun receiveOrNull(): E? {
+ // fast path -- try poll non-blocking
+ val result = pollInternal()
+ if (result !== POLL_FAILED) return receiveOrNullResult(result)
+ // 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> {
+ // fast path -- try poll non-blocking
+ val result = pollInternal()
+ if (result !== POLL_FAILED) return result.toResult()
+ // slow-path does suspend
+ return receiveSuspend(RECEIVE_RESULT)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ public final override fun poll(): E? {
+ val result = pollInternal()
+ return if (result === POLL_FAILED) null else receiveOrNullResult(result)
+ }
+
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ final override fun cancel(cause: Throwable?): Boolean =
+ cancelInternal(cause)
+
+ final override fun cancel(cause: CancellationException?) {
+ cancelInternal(cause ?: CancellationException("$classSimpleName was cancelled"))
+ }
+
+ // It needs to be internal to support deprecated cancel(Throwable?) API
+ internal open fun cancelInternal(cause: Throwable?): Boolean =
+ close(cause).also {
+ cleanupSendQueueOnCancel()
+ }
+
+ // Note: this function is invoked when channel is already closed
+ protected open fun cleanupSendQueueOnCancel() {
+ val closed = closedForSend ?: error("Cannot happen")
+ while (true) {
+ val send = takeFirstSendOrPeekClosed() ?: error("Cannot happen")
+ if (send is Closed<*>) {
+ assert { send === closed }
+ return // cleaned
+ }
+ send.resumeSendClosed(closed)
+ }
+ }
+
+ public final override fun iterator(): ChannelIterator<E> = Itr(this)
+
+ // ------ registerSelectReceive ------
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected fun describeTryPoll(): TryPollDesc<E> = TryPollDesc(queue)
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected class TryPollDesc<E>(queue: LockFreeLinkedListHead) : RemoveFirstDesc<Send>(queue) {
+ @JvmField var resumeToken: Any? = null
+ @JvmField var pollResult: E? = null
+
+ override fun failure(affected: LockFreeLinkedListNode): Any? = when (affected) {
+ is Closed<*> -> affected
+ !is Send -> POLL_FAILED
+ else -> null
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun validatePrepared(node: Send): Boolean {
+ val token = node.tryResumeSend(idempotent = this) ?: return false
+ resumeToken = token
+ pollResult = node.pollResult as E
+ return true
+ }
+ }
+
+ final override val onReceive: SelectClause1<E>
+ get() = object : SelectClause1<E> {
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E) -> R) {
+ registerSelectReceive(select, block)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <R> registerSelectReceive(select: SelectInstance<R>, block: suspend (E) -> R) {
+ while (true) {
+ if (select.isSelected) return
+ if (isEmpty) {
+ if (enqueueReceiveSelect(select, block as suspend (Any?) -> R, RECEIVE_THROWS_ON_CLOSE)) return
+ } else {
+ val pollResult = pollSelectInternal(select)
+ when {
+ pollResult === ALREADY_SELECTED -> return
+ pollResult === POLL_FAILED -> {} // retry
+ pollResult is Closed<*> -> throw recoverStackTrace(pollResult.receiveException)
+ else -> {
+ block.startCoroutineUnintercepted(pollResult as E, select.completion)
+ return
+ }
+ }
+ }
+ }
+ }
+
+ final override val onReceiveOrNull: SelectClause1<E?>
+ get() = object : SelectClause1<E?> {
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E?) -> R) {
+ registerSelectReceiveOrNull(select, block)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <R> registerSelectReceiveOrNull(select: SelectInstance<R>, block: suspend (E?) -> R) {
+ while (true) {
+ if (select.isSelected) return
+ if (isEmpty) {
+ if (enqueueReceiveSelect(select, block as suspend (Any?) -> R, RECEIVE_NULL_ON_CLOSE)) return
+ } else {
+ val pollResult = pollSelectInternal(select)
+ when {
+ pollResult === ALREADY_SELECTED -> return
+ pollResult === POLL_FAILED -> {} // retry
+ pollResult is Closed<*> -> {
+ if (pollResult.closeCause == null) {
+ if (select.trySelect(null))
+ block.startCoroutineUnintercepted(null, select.completion)
+ return
+ } else {
+ throw recoverStackTrace(pollResult.closeCause)
+ }
+ }
+ else -> {
+ // selected successfully
+ block.startCoroutineUnintercepted(pollResult as E, select.completion)
+ return
+ }
+ }
+ }
+ }
+ }
+
+ override val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
+ get() = object : SelectClause1<ValueOrClosed<E>> {
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (ValueOrClosed<E>) -> R) {
+ registerSelectReceiveOrClosed(select, block)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <R> registerSelectReceiveOrClosed(select: SelectInstance<R>, block: suspend (ValueOrClosed<E>) -> R) {
+ while (true) {
+ if (select.isSelected) return
+ if (isEmpty) {
+ if (enqueueReceiveSelect(select, block as suspend (Any?) -> R, RECEIVE_RESULT)) return
+ } else {
+ val pollResult = pollSelectInternal(select)
+ when {
+ pollResult === ALREADY_SELECTED -> return
+ pollResult === POLL_FAILED -> {} // retry
+ pollResult is Closed<*> -> {
+ block.startCoroutineUnintercepted(ValueOrClosed.closed(pollResult.closeCause), select.completion)
+ }
+ else -> {
+ // selected successfully
+ block.startCoroutineUnintercepted(ValueOrClosed.value(pollResult as E), select.completion)
+ return
+ }
+ }
+ }
+ }
+ }
+
+ private fun <R> enqueueReceiveSelect(
+ select: SelectInstance<R>,
+ block: suspend (Any?) -> R,
+ receiveMode: Int
+ ): Boolean {
+ val node = ReceiveSelect(this, select, block, receiveMode)
+ val result = enqueueReceive(node)
+ if (result) select.disposeOnSelect(node)
+ return result
+ }
+
+ // ------ protected ------
+
+ override fun takeFirstReceiveOrPeekClosed(): ReceiveOrClosed<E>? =
+ super.takeFirstReceiveOrPeekClosed().also {
+ if (it != null && it !is Closed<*>) onReceiveDequeued()
+ }
+
+ /**
+ * Invoked when receiver is successfully enqueued to the queue of waiting receivers.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun onReceiveEnqueued() {}
+
+ /**
+ * Invoked when enqueued receiver was successfully removed from the queue of waiting receivers.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun onReceiveDequeued() {}
+
+ // ------ private ------
+
+ private fun removeReceiveOnCancel(cont: CancellableContinuation<*>, receive: Receive<*>) =
+ cont.invokeOnCancellation(handler = RemoveReceiveOnCancel(receive).asHandler)
+
+ private inner class RemoveReceiveOnCancel(private val receive: Receive<*>) : CancelHandler() {
+ override fun invoke(cause: Throwable?) {
+ if (receive.remove())
+ onReceiveDequeued()
+ }
+ override fun toString(): String = "RemoveReceiveOnCancel[$receive]"
+ }
+
+ private class Itr<E>(val channel: AbstractChannel<E>) : ChannelIterator<E> {
+ var result: Any? = POLL_FAILED // E | POLL_FAILED | Closed
+
+ override suspend fun hasNext(): Boolean {
+ // check for repeated hasNext
+ if (result !== POLL_FAILED) return hasNextResult(result)
+ // fast path -- try poll non-blocking
+ result = channel.pollInternal()
+ if (result !== POLL_FAILED) return hasNextResult(result)
+ // slow-path does suspend
+ return hasNextSuspend()
+ }
+
+ private fun hasNextResult(result: Any?): Boolean {
+ if (result is Closed<*>) {
+ if (result.closeCause != null) throw recoverStackTrace(result.receiveException)
+ return false
+ }
+ return true
+ }
+
+ private suspend fun hasNextSuspend(): Boolean = suspendAtomicCancellableCoroutine sc@ { cont ->
+ val receive = ReceiveHasNext(this, cont)
+ while (true) {
+ if (channel.enqueueReceive(receive)) {
+ channel.removeReceiveOnCancel(cont, receive)
+ return@sc
+ }
+ // hm... something is not right. try to poll
+ val result = channel.pollInternal()
+ this.result = result
+ if (result is Closed<*>) {
+ if (result.closeCause == null)
+ cont.resume(false)
+ else
+ cont.resumeWithException(result.receiveException)
+ return@sc
+ }
+ if (result !== POLL_FAILED) {
+ cont.resume(true)
+ return@sc
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun next(): E {
+ val result = this.result
+ if (result is Closed<*>) throw recoverStackTrace(result.receiveException)
+ if (result !== POLL_FAILED) {
+ this.result = POLL_FAILED
+ return result as E
+ }
+
+ throw IllegalStateException("'hasNext' should be called prior to 'next' invocation")
+ }
+ }
+
+ private class ReceiveElement<in E>(
+ @JvmField val cont: CancellableContinuation<Any?>,
+ @JvmField val receiveMode: Int
+ ) : Receive<E>() {
+ fun resumeValue(value: E): Any? = when (receiveMode) {
+ RECEIVE_RESULT -> ValueOrClosed.value(value)
+ else -> value
+ }
+
+ @Suppress("IMPLICIT_CAST_TO_ANY")
+ override fun tryResumeReceive(value: E, idempotent: Any?): Any? = cont.tryResume(resumeValue(value), idempotent)
+ override fun completeResumeReceive(token: Any) = cont.completeResume(token)
+ 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)
+ }
+ }
+ override fun toString(): String = "ReceiveElement[receiveMode=$receiveMode]"
+ }
+
+ private class ReceiveHasNext<E>(
+ @JvmField val iterator: Itr<E>,
+ @JvmField val cont: CancellableContinuation<Boolean>
+ ) : Receive<E>() {
+ override fun tryResumeReceive(value: E, idempotent: Any?): Any? {
+ val token = cont.tryResume(true, idempotent)
+ if (token != null) {
+ /*
+ When idempotent != null this invocation can be stale and we cannot directly update iterator.result
+ Instead, we save both token & result into a temporary IdempotentTokenValue object and
+ set iterator result only in completeResumeReceive that is going to be invoked just once
+ */
+ if (idempotent != null) return IdempotentTokenValue(token, value)
+ iterator.result = value
+ }
+ return token
+ }
+
+ override fun completeResumeReceive(token: Any) {
+ if (token is IdempotentTokenValue<*>) {
+ iterator.result = token.value
+ cont.completeResume(token.token)
+ } else
+ cont.completeResume(token)
+ }
+
+ override fun resumeReceiveClosed(closed: Closed<*>) {
+ val token = if (closed.closeCause == null) {
+ cont.tryResume(false)
+ } else {
+ cont.tryResumeWithException(recoverStackTrace(closed.receiveException, cont))
+ }
+ if (token != null) {
+ iterator.result = closed
+ cont.completeResume(token)
+ }
+ }
+ override fun toString(): String = "ReceiveHasNext"
+ }
+
+ private class ReceiveSelect<R, E>(
+ @JvmField val channel: AbstractChannel<E>,
+ @JvmField val select: SelectInstance<R>,
+ @JvmField val block: suspend (Any?) -> R,
+ @JvmField val receiveMode: Int
+ ) : Receive<E>(), DisposableHandle {
+ override fun tryResumeReceive(value: E, idempotent: Any?): Any? =
+ if (select.trySelect(idempotent)) (value ?: NULL_VALUE) else null
+
+ @Suppress("UNCHECKED_CAST")
+ override fun completeResumeReceive(token: Any) {
+ val value: E = NULL_VALUE.unbox<E>(token)
+ block.startCoroutine(if (receiveMode == RECEIVE_RESULT) ValueOrClosed.value(value) else value, select.completion)
+ }
+
+ override fun resumeReceiveClosed(closed: Closed<*>) {
+ if (!select.trySelect(null)) return
+ when (receiveMode) {
+ RECEIVE_THROWS_ON_CLOSE -> select.resumeSelectCancellableWithException(closed.receiveException)
+ RECEIVE_RESULT -> block.startCoroutine(ValueOrClosed.closed<R>(closed.closeCause), select.completion)
+ RECEIVE_NULL_ON_CLOSE -> if (closed.closeCause == null) {
+ block.startCoroutine(null, select.completion)
+ } else {
+ select.resumeSelectCancellableWithException(closed.receiveException)
+ }
+ }
+ }
+
+ override fun dispose() { // invoked on select completion
+ if (remove())
+ channel.onReceiveDequeued() // notify cancellation of receive
+ }
+
+ override fun toString(): String = "ReceiveSelect[$select,receiveMode=$receiveMode]"
+ }
+
+ private class IdempotentTokenValue<out E>(
+ @JvmField val token: Any,
+ @JvmField val value: 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
+
+@JvmField
+@SharedImmutable
+internal val OFFER_SUCCESS: Any = Symbol("OFFER_SUCCESS")
+
+@JvmField
+@SharedImmutable
+internal val OFFER_FAILED: Any = Symbol("OFFER_FAILED")
+
+@JvmField
+@SharedImmutable
+internal val POLL_FAILED: Any = Symbol("POLL_FAILED")
+
+@JvmField
+@SharedImmutable
+internal val ENQUEUE_FAILED: Any = Symbol("ENQUEUE_FAILED")
+
+@JvmField
+@SharedImmutable
+internal val SELECT_STARTED: Any = Symbol("SELECT_STARTED")
+
+@JvmField
+@SharedImmutable
+internal val NULL_VALUE: Symbol = Symbol("NULL_VALUE")
+
+@JvmField
+@SharedImmutable
+internal val CLOSE_RESUMED: Any = Symbol("CLOSE_RESUMED")
+
+@JvmField
+@SharedImmutable
+internal val SEND_RESUMED: Any = Symbol("SEND_RESUMED")
+
+@JvmField
+@SharedImmutable
+internal val HANDLER_INVOKED: Any = Symbol("ON_CLOSE_HANDLER_INVOKED")
+
+internal typealias Handler = (Throwable?) -> Unit
+
+/**
+ * Represents sending waiter in the queue.
+ */
+internal abstract class Send : LockFreeLinkedListNode() {
+ abstract val pollResult: Any? // E | Closed
+ abstract fun tryResumeSend(idempotent: Any?): Any?
+ abstract fun completeResumeSend(token: Any)
+ abstract fun resumeSendClosed(closed: Closed<*>)
+}
+
+/**
+ * Represents receiver waiter in the queue or closed token.
+ */
+internal interface ReceiveOrClosed<in E> {
+ val offerResult: Any // OFFER_SUCCESS | Closed
+ fun tryResumeReceive(value: E, idempotent: Any?): Any?
+ fun completeResumeReceive(token: Any)
+}
+
+/**
+ * Represents sender for a specific element.
+ */
+@Suppress("UNCHECKED_CAST")
+internal class SendElement(
+ override val pollResult: Any?,
+ @JvmField val cont: CancellableContinuation<Unit>
+) : Send() {
+ override fun tryResumeSend(idempotent: Any?): Any? = cont.tryResume(Unit, idempotent)
+ override fun completeResumeSend(token: Any) = cont.completeResume(token)
+ override fun resumeSendClosed(closed: Closed<*>) = cont.resumeWithException(closed.sendException)
+ override fun toString(): String = "SendElement($pollResult)"
+}
+
+/**
+ * Represents closed channel.
+ */
+internal class Closed<in E>(
+ @JvmField val closeCause: Throwable?
+) : Send(), ReceiveOrClosed<E> {
+ val sendException: Throwable get() = closeCause ?: ClosedSendChannelException(DEFAULT_CLOSE_MESSAGE)
+ val receiveException: Throwable get() = closeCause ?: ClosedReceiveChannelException(DEFAULT_CLOSE_MESSAGE)
+
+ override val offerResult get() = this
+ override val pollResult get() = this
+ override fun tryResumeSend(idempotent: Any?): Any? = CLOSE_RESUMED
+ override fun completeResumeSend(token: Any) { assert { token === CLOSE_RESUMED } }
+ override fun tryResumeReceive(value: E, idempotent: Any?): Any? = CLOSE_RESUMED
+ override fun completeResumeReceive(token: Any) { assert { token === CLOSE_RESUMED } }
+ override fun resumeSendClosed(closed: Closed<*>) = assert { false } // "Should be never invoked"
+ override fun toString(): String = "Closed[$closeCause]"
+}
+
+private abstract class Receive<in E> : LockFreeLinkedListNode(), ReceiveOrClosed<E> {
+ override val offerResult get() = OFFER_SUCCESS
+ abstract fun resumeReceiveClosed(closed: Closed<*>)
+}
+
+@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)
+
+@Suppress("NOTHING_TO_INLINE")
+private inline fun <E> Closed<*>.toResult(): ValueOrClosed<E> = ValueOrClosed.closed(closeCause)
diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
new file mode 100644
index 00000000..7fbf0c36
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.internal.*
+import kotlinx.coroutines.selects.*
+import kotlin.jvm.*
+
+/**
+ * Broadcast channel with array buffer of a fixed [capacity].
+ * Sender suspends only when buffer is full due to one of the receives being slow to consume and
+ * receiver suspends only when buffer is empty.
+ *
+ * **Note**, that elements that are sent to this channel while there are no
+ * [openSubscription] subscribers are immediately lost.
+ *
+ * This channel is created by `BroadcastChannel(capacity)` factory function invocation.
+ *
+ * This implementation uses lock to protect the buffer, which is held only during very short buffer-update operations.
+ * The lock at each subscription is also used to manage concurrent attempts to receive from the same subscriber.
+ * The lists of suspended senders or receivers are lock-free.
+ */
+internal class ArrayBroadcastChannel<E>(
+ /**
+ * Buffer capacity.
+ */
+ val capacity: Int
+) : AbstractSendChannel<E>(), BroadcastChannel<E> {
+ init {
+ require(capacity >= 1) { "ArrayBroadcastChannel capacity must be at least 1, but $capacity was specified" }
+ }
+
+ /*
+ * Writes to buffer are guarded by bufferLock, but reads from buffer are concurrent with writes
+ * - Write element to buffer then write "tail" (volatile)
+ * - Read "tail" (volatile), then read element from buffer
+ * So read/writes to buffer need not be volatile
+ */
+ private val bufferLock = ReentrantLock()
+ private val buffer = arrayOfNulls<Any?>(capacity)
+
+ // head & tail are Long (64 bits) and we assume that they never wrap around
+ // head, tail, and size are guarded by bufferLock
+ @Volatile
+ private var head: Long = 0 // do modulo on use of head
+ @Volatile
+ private var tail: Long = 0 // do modulo on use of tail
+ @Volatile
+ private var size: Int = 0
+
+ private val subscribers = subscriberList<Subscriber<E>>()
+
+ override val isBufferAlwaysFull: Boolean get() = false
+ override val isBufferFull: Boolean get() = size >= capacity
+
+ public override fun openSubscription(): ReceiveChannel<E> =
+ Subscriber(this).also {
+ updateHead(addSub = it)
+ }
+
+ public override fun close(cause: Throwable?): Boolean {
+ if (!super.close(cause)) return false
+ checkSubOffers()
+ return true
+ }
+
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ override fun cancel(cause: Throwable?): Boolean =
+ cancelInternal(cause)
+
+ override fun cancel(cause: CancellationException?) {
+ cancelInternal(cause)
+ }
+
+ private fun cancelInternal(cause: Throwable?): Boolean =
+ close(cause).also {
+ for (sub in subscribers) sub.cancelInternal(cause)
+ }
+
+ // result is `OFFER_SUCCESS | OFFER_FAILED | Closed`
+ override fun offerInternal(element: E): Any {
+ bufferLock.withLock {
+ // check if closed for send (under lock, so size cannot change)
+ closedForSend?.let { return it }
+ val size = this.size
+ if (size >= capacity) return OFFER_FAILED
+ val tail = this.tail
+ buffer[(tail % capacity).toInt()] = element
+ this.size = size + 1
+ this.tail = tail + 1
+ }
+ // if offered successfully, then check subscribers outside of lock
+ checkSubOffers()
+ return OFFER_SUCCESS
+ }
+
+ // result is `ALREADY_SELECTED | OFFER_SUCCESS | OFFER_FAILED | Closed`
+ override fun offerSelectInternal(element: E, select: SelectInstance<*>): Any {
+ bufferLock.withLock {
+ // check if closed for send (under lock, so size cannot change)
+ closedForSend?.let { return it }
+ val size = this.size
+ if (size >= capacity) return OFFER_FAILED
+ // let's try to select sending this element to buffer
+ if (!select.trySelect(null)) { // :todo: move trySelect completion outside of lock
+ return ALREADY_SELECTED
+ }
+ val tail = this.tail
+ buffer[(tail % capacity).toInt()] = element
+ this.size = size + 1
+ this.tail = tail + 1
+ }
+ // if offered successfully, then check subscribers outside of lock
+ checkSubOffers()
+ return OFFER_SUCCESS
+ }
+
+ private fun checkSubOffers() {
+ var updated = false
+ var hasSubs = false
+ @Suppress("LoopToCallChain") // must invoke `checkOffer` on every sub
+ for (sub in subscribers) {
+ hasSubs = true
+ if (sub.checkOffer()) updated = true
+ }
+ if (updated || !hasSubs)
+ updateHead()
+ }
+
+ // updates head if needed and optionally adds / removes subscriber under the same lock
+ private tailrec fun updateHead(addSub: Subscriber<E>? = null, removeSub: Subscriber<E>? = null) {
+ // update head in a tail rec loop
+ var send: Send? = null
+ var token: Any? = null
+ bufferLock.withLock {
+ if (addSub != null) {
+ addSub.subHead = tail // start from last element
+ val wasEmpty = subscribers.isEmpty()
+ subscribers.add(addSub)
+ if (!wasEmpty) return // no need to update when adding second and etc sub
+ }
+ if (removeSub != null) {
+ subscribers.remove(removeSub)
+ if (head != removeSub.subHead) return // no need to update
+ }
+ val minHead = computeMinHead()
+ val tail = this.tail
+ var head = this.head
+ val targetHead = minHead.coerceAtMost(tail)
+ if (targetHead <= head) return // nothing to do -- head was already moved
+ var size = this.size
+ // clean up removed (on not need if we don't have any subscribers anymore)
+ while (head < targetHead) {
+ buffer[(head % capacity).toInt()] = null
+ val wasFull = size >= capacity
+ // update the size before checking queue (no more senders can queue up)
+ this.head = ++head
+ this.size = --size
+ if (wasFull) {
+ while (true) {
+ send = takeFirstSendOrPeekClosed() ?: break // when when no sender
+ if (send is Closed<*>) break // break when closed for send
+ token = send!!.tryResumeSend(idempotent = null)
+ if (token != null) {
+ // put sent element to the buffer
+ buffer[(tail % capacity).toInt()] = (send as Send).pollResult
+ this.size = size + 1
+ this.tail = tail + 1
+ return@withLock // go out of lock to wakeup this sender
+ }
+ }
+ }
+ }
+ return // done updating here -> return
+ }
+ // we only get out of the lock normally when there is a sender to resume
+ send!!.completeResumeSend(token!!)
+ // since we've just sent an element, we might need to resume some receivers
+ checkSubOffers()
+ // tailrec call to recheck
+ updateHead()
+ }
+
+ private fun computeMinHead(): Long {
+ var minHead = Long.MAX_VALUE
+ for (sub in subscribers)
+ minHead = minHead.coerceAtMost(sub.subHead) // volatile (atomic) reads of subHead
+ return minHead
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun elementAt(index: Long): E = buffer[(index % capacity).toInt()] as E
+
+ private class Subscriber<E>(
+ private val broadcastChannel: ArrayBroadcastChannel<E>
+ ) : AbstractChannel<E>(), ReceiveChannel<E> {
+ private val subLock = ReentrantLock()
+
+ @Volatile
+ @JvmField
+ var subHead: Long = 0 // guarded by subLock
+
+ override val isBufferAlwaysEmpty: Boolean get() = false
+ override val isBufferEmpty: Boolean get() = subHead >= broadcastChannel.tail
+ override val isBufferAlwaysFull: Boolean get() = error("Should not be used")
+ override val isBufferFull: Boolean get() = error("Should not be used")
+
+ override fun cancelInternal(cause: Throwable?): Boolean =
+ close(cause).also { closed ->
+ if (closed) broadcastChannel.updateHead(removeSub = this)
+ clearBuffer()
+ }
+
+ private fun clearBuffer() {
+ subLock.withLock {
+ subHead = broadcastChannel.tail
+ }
+ }
+
+ // returns true if subHead was updated and broadcast channel's head must be checked
+ // this method is lock-free (it never waits on lock)
+ @Suppress("UNCHECKED_CAST")
+ fun checkOffer(): Boolean {
+ var updated = false
+ var closed: Closed<*>? = null
+ loop@
+ while (needsToCheckOfferWithoutLock()) {
+ // just use `tryLock` here and break when some other thread is checking under lock
+ // it means that `checkOffer` must be retried after every `unlock`
+ if (!subLock.tryLock()) break
+ val receive: ReceiveOrClosed<E>?
+ val token: Any?
+ try {
+ val result = peekUnderLock()
+ when {
+ result === POLL_FAILED -> continue@loop // must retest `needsToCheckOfferWithoutLock` outside of the lock
+ result is Closed<*> -> {
+ closed = result
+ break@loop // was closed
+ }
+ }
+ // find a receiver for an element
+ receive = takeFirstReceiveOrPeekClosed() ?: break // break when no one's receiving
+ if (receive is Closed<*>) break // noting more to do if this sub already closed
+ token = receive.tryResumeReceive(result as E, idempotent = null)
+ if (token == null) continue // bail out here to next iteration (see for next receiver)
+ val subHead = this.subHead
+ this.subHead = subHead + 1 // retrieved element for this subscriber
+ updated = true
+ } finally {
+ subLock.unlock()
+ }
+ receive!!.completeResumeReceive(token!!)
+ }
+ // do close outside of lock if needed
+ closed?.also { close(cause = it.closeCause) }
+ return updated
+ }
+
+ // result is `E | POLL_FAILED | Closed`
+ override fun pollInternal(): Any? {
+ var updated = false
+ val result = subLock.withLock {
+ val result = peekUnderLock()
+ when {
+ result is Closed<*> -> { /* just bail out of lock */ }
+ result === POLL_FAILED -> { /* just bail out of lock */ }
+ else -> {
+ // update subHead after retrieiving element from buffer
+ val subHead = this.subHead
+ this.subHead = subHead + 1
+ updated = true
+ }
+ }
+ result
+ }
+ // do close outside of lock
+ (result as? Closed<*>)?.also { close(cause = it.closeCause) }
+ // there could have been checkOffer attempt while we were holding lock
+ // now outside the lock recheck if anything else to offer
+ if (checkOffer())
+ updated = true
+ // and finally update broadcast's channel head if needed
+ if (updated)
+ broadcastChannel.updateHead()
+ return result
+ }
+
+ // result is `ALREADY_SELECTED | E | POLL_FAILED | Closed`
+ override fun pollSelectInternal(select: SelectInstance<*>): Any? {
+ var updated = false
+ val result = subLock.withLock {
+ var result = peekUnderLock()
+ when {
+ result is Closed<*> -> { /* just bail out of lock */ }
+ result === POLL_FAILED -> { /* just bail out of lock */ }
+ else -> {
+ // let's try to select receiving this element from buffer
+ if (!select.trySelect(null)) { // :todo: move trySelect completion outside of lock
+ result = ALREADY_SELECTED
+ } else {
+ // update subHead after retrieiving element from buffer
+ val subHead = this.subHead
+ this.subHead = subHead + 1
+ updated = true
+ }
+ }
+ }
+ result
+ }
+ // do close outside of lock
+ (result as? Closed<*>)?.also { close(cause = it.closeCause) }
+ // there could have been checkOffer attempt while we were holding lock
+ // now outside the lock recheck if anything else to offer
+ if (checkOffer())
+ updated = true
+ // and finally update broadcast's channel head if needed
+ if (updated)
+ broadcastChannel.updateHead()
+ return result
+ }
+
+ // Must invoke this check this after lock, because offer's invocation of `checkOffer` might have failed
+ // to `tryLock` just before the lock was about to unlocked, thus loosing notification to this
+ // subscription about an element that was just offered
+ private fun needsToCheckOfferWithoutLock(): Boolean {
+ if (closedForReceive != null)
+ return false // already closed -> nothing to do
+ if (isBufferEmpty && broadcastChannel.closedForReceive == null)
+ return false // no data for us && broadcast channel was not closed yet -> nothing to do
+ return true // check otherwise
+ }
+
+ // guarded by lock, returns:
+ // E - the element from the buffer at subHead
+ // Closed<*> when closed;
+ // POLL_FAILED when there seems to be no data, but must retest `needsToCheckOfferWithoutLock` outside of lock
+ private fun peekUnderLock(): Any? {
+ val subHead = this.subHead // guarded read (can be non-volatile read)
+ // note: from the broadcastChannel we must read closed token first, then read its tail
+ // because it is Ok if tail moves in between the reads (we make decision based on tail first)
+ val closedBroadcast = broadcastChannel.closedForReceive // unguarded volatile read
+ val tail = broadcastChannel.tail // unguarded volatile read
+ if (subHead >= tail) {
+ // no elements to poll from the queue -- check if closed broads & closed this sub
+ // must retest `needsToCheckOfferWithoutLock` outside of the lock
+ return closedBroadcast ?: this.closedForReceive ?: POLL_FAILED
+ }
+ // Get tentative result. This result may be wrong (completely invalid value, including null),
+ // because this subscription might get closed, moving channel's head past this subscription's head.
+ val result = broadcastChannel.elementAt(subHead)
+ // now check if this subscription was closed
+ val closedSub = this.closedForReceive
+ if (closedSub != null) return closedSub
+ // we know the subscription was not closed, so this tentative result is Ok to return
+ return result
+ }
+ }
+
+ // ------ debug ------
+
+ override val bufferDebugString: String
+ get() = "(buffer:capacity=${buffer.size},size=$size)"
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt
new file mode 100644
index 00000000..1e1c0d3a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt
@@ -0,0 +1,261 @@
+/*
+ * 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 kotlinx.coroutines.internal.*
+import kotlinx.coroutines.selects.*
+import kotlin.jvm.*
+import kotlin.math.*
+
+/**
+ * Channel with array buffer of a fixed [capacity].
+ * Sender suspends only when buffer is full and receiver suspends only when buffer is empty.
+ *
+ * This channel is created by `Channel(capacity)` factory function invocation.
+ *
+ * This implementation uses lock to protect the buffer, which is held only during very short buffer-update operations.
+ * The lists of suspended senders or receivers are lock-free.
+ **/
+internal open class ArrayChannel<E>(
+ /**
+ * Buffer capacity.
+ */
+ val capacity: Int
+) : AbstractChannel<E>() {
+ init {
+ require(capacity >= 1) { "ArrayChannel capacity must be at least 1, but $capacity was specified" }
+ }
+
+ private val lock = ReentrantLock()
+ /*
+ * Guarded by lock.
+ * Allocate minimum of capacity and 16 to avoid excess memory pressure for large channels when it's not necessary.
+ */
+ private var buffer: Array<Any?> = arrayOfNulls<Any?>(min(capacity, 8))
+ private var head: Int = 0
+ @Volatile
+ private var size: Int = 0 // Invariant: size <= capacity
+
+ protected final override val isBufferAlwaysEmpty: Boolean get() = false
+ protected final override val isBufferEmpty: Boolean get() = size == 0
+ protected final override val isBufferAlwaysFull: Boolean get() = false
+ protected final override val isBufferFull: Boolean get() = size == capacity
+
+ // result is `OFFER_SUCCESS | OFFER_FAILED | Closed`
+ protected override fun offerInternal(element: E): Any {
+ var receive: ReceiveOrClosed<E>? = null
+ var token: Any? = null
+ lock.withLock {
+ val size = this.size
+ closedForSend?.let { return it }
+ if (size < capacity) {
+ // tentatively put element to buffer
+ this.size = size + 1 // update size before checking queue (!!!)
+ // check for receivers that were waiting on empty queue
+ if (size == 0) {
+ loop@ while (true) {
+ receive = takeFirstReceiveOrPeekClosed() ?: break@loop // break when no receivers queued
+ if (receive is Closed) {
+ this.size = size // restore size
+ return receive!!
+ }
+ token = receive!!.tryResumeReceive(element, idempotent = null)
+ if (token != null) {
+ this.size = size // restore size
+ return@withLock
+ }
+ }
+ }
+ ensureCapacity(size)
+ buffer[(head + size) % buffer.size] = element // actually queue element
+ return OFFER_SUCCESS
+ }
+ // size == capacity: full
+ return OFFER_FAILED
+ }
+ // breaks here if offer meets receiver
+ receive!!.completeResumeReceive(token!!)
+ return receive!!.offerResult
+ }
+
+ // result is `ALREADY_SELECTED | OFFER_SUCCESS | OFFER_FAILED | Closed`
+ protected override fun offerSelectInternal(element: E, select: SelectInstance<*>): Any {
+ var receive: ReceiveOrClosed<E>? = null
+ var token: Any? = null
+ lock.withLock {
+ val size = this.size
+ closedForSend?.let { return it }
+ if (size < capacity) {
+ // tentatively put element to buffer
+ this.size = size + 1 // update size before checking queue (!!!)
+ // check for receivers that were waiting on empty queue
+ if (size == 0) {
+ loop@ while (true) {
+ val offerOp = describeTryOffer(element)
+ val failure = select.performAtomicTrySelect(offerOp)
+ when {
+ failure == null -> { // offered successfully
+ this.size = size // restore size
+ receive = offerOp.result
+ token = offerOp.resumeToken
+ assert { token != null }
+ return@withLock
+ }
+ failure === OFFER_FAILED -> break@loop // cannot offer -> Ok to queue to buffer
+ failure === ALREADY_SELECTED || failure is Closed<*> -> {
+ this.size = size // restore size
+ return failure
+ }
+ else -> error("performAtomicTrySelect(describeTryOffer) returned $failure")
+ }
+ }
+ }
+ // let's try to select sending this element to buffer
+ if (!select.trySelect(null)) { // :todo: move trySelect completion outside of lock
+ this.size = size // restore size
+ return ALREADY_SELECTED
+ }
+ ensureCapacity(size)
+ buffer[(head + size) % buffer.size] = element // actually queue element
+ return OFFER_SUCCESS
+ }
+ // size == capacity: full
+ return OFFER_FAILED
+ }
+ // breaks here if offer meets receiver
+ receive!!.completeResumeReceive(token!!)
+ return receive!!.offerResult
+ }
+
+ // Guarded by lock
+ private fun ensureCapacity(currentSize: Int) {
+ if (currentSize >= buffer.size) {
+ val newSize = min(buffer.size * 2, capacity)
+ val newBuffer = arrayOfNulls<Any?>(newSize)
+ for (i in 0 until currentSize) {
+ newBuffer[i] = buffer[(head + i) % buffer.size]
+ }
+ buffer = newBuffer
+ head = 0
+ }
+ }
+
+ // result is `E | POLL_FAILED | Closed`
+ protected override fun pollInternal(): Any? {
+ var send: Send? = null
+ var token: Any? = null
+ var result: Any? = null
+ lock.withLock {
+ val size = this.size
+ if (size == 0) return closedForSend ?: POLL_FAILED // when nothing can be read from buffer
+ // size > 0: not empty -- retrieve element
+ result = buffer[head]
+ buffer[head] = null
+ this.size = size - 1 // update size before checking queue (!!!)
+ // check for senders that were waiting on full queue
+ var replacement: Any? = POLL_FAILED
+ if (size == capacity) {
+ loop@ while (true) {
+ send = takeFirstSendOrPeekClosed() ?: break
+ token = send!!.tryResumeSend(idempotent = null)
+ if (token != null) {
+ replacement = send!!.pollResult
+ break@loop
+ }
+ }
+ }
+ if (replacement !== POLL_FAILED && replacement !is Closed<*>) {
+ this.size = size // restore size
+ buffer[(head + size) % buffer.size] = replacement
+ }
+ head = (head + 1) % buffer.size
+ }
+ // complete send the we're taken replacement from
+ if (token != null)
+ send!!.completeResumeSend(token!!)
+ return result
+ }
+
+ // result is `ALREADY_SELECTED | E | POLL_FAILED | Closed`
+ protected override fun pollSelectInternal(select: SelectInstance<*>): Any? {
+ var send: Send? = null
+ var token: Any? = null
+ var result: Any? = null
+ lock.withLock {
+ val size = this.size
+ if (size == 0) return closedForSend ?: POLL_FAILED
+ // size > 0: not empty -- retrieve element
+ result = buffer[head]
+ buffer[head] = null
+ this.size = size - 1 // update size before checking queue (!!!)
+ // check for senders that were waiting on full queue
+ var replacement: Any? = POLL_FAILED
+ if (size == capacity) {
+ loop@ while (true) {
+ val pollOp = describeTryPoll()
+ val failure = select.performAtomicTrySelect(pollOp)
+ when {
+ failure == null -> { // polled successfully
+ send = pollOp.result
+ token = pollOp.resumeToken
+ assert { token != null }
+ replacement = send!!.pollResult
+ break@loop
+ }
+ failure === POLL_FAILED -> break@loop // cannot poll -> Ok to take from buffer
+ failure === ALREADY_SELECTED -> {
+ this.size = size // restore size
+ buffer[head] = result // restore head
+ return failure
+ }
+ failure is Closed<*> -> {
+ send = failure
+ token = failure.tryResumeSend(idempotent = null)
+ replacement = failure
+ break@loop
+ }
+ else -> error("performAtomicTrySelect(describeTryOffer) returned $failure")
+ }
+ }
+ }
+ if (replacement !== POLL_FAILED && replacement !is Closed<*>) {
+ this.size = size // restore size
+ buffer[(head + size) % buffer.size] = replacement
+ } else {
+ // failed to poll or is already closed --> let's try to select receiving this element from buffer
+ if (!select.trySelect(null)) { // :todo: move trySelect completion outside of lock
+ this.size = size // restore size
+ buffer[head] = result // restore head
+ return ALREADY_SELECTED
+ }
+ }
+ head = (head + 1) % buffer.size
+ }
+ // complete send the we're taken replacement from
+ if (token != null)
+ send!!.completeResumeSend(token!!)
+ return result
+ }
+
+ // Note: this function is invoked when channel is already closed
+ override fun cleanupSendQueueOnCancel() {
+ // clear buffer first
+ lock.withLock {
+ repeat(size) {
+ buffer[head] = 0
+ head = (head + 1) % buffer.size
+ }
+ size = 0
+ }
+ // then clean all queued senders
+ super.cleanupSendQueueOnCancel()
+ }
+
+ // ------ debug ------
+
+ override val bufferDebugString: String
+ get() = "(buffer:capacity=$capacity,size=$size)"
+}
diff --git a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt
new file mode 100644
index 00000000..4ad6b8c4
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.channels.Channel.Factory.CONFLATED
+import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
+import kotlinx.coroutines.intrinsics.*
+import kotlin.coroutines.*
+
+/**
+ * Broadcasts all elements of the channel.
+ *
+ * The kind of the resulting channel depends on the specified [capacity] parameter:
+ * when `capacity` is positive (1 by default), but less than [UNLIMITED] -- uses `ArrayBroadcastChannel` with a buffer of given capacity,
+ * when `capacity` is [CONFLATED] -- uses [ConflatedBroadcastChannel] that conflates back-to-back sends;
+ * Note that resulting channel behaves like [ConflatedBroadcastChannel] but is not an instance of [ConflatedBroadcastChannel].
+ * otherwise -- throws [IllegalArgumentException].
+ *
+ * @param start coroutine start option. The default value is [CoroutineStart.LAZY].
+ */
+fun <E> ReceiveChannel<E>.broadcast(
+ capacity: Int = 1,
+ start: CoroutineStart = CoroutineStart.LAZY
+): BroadcastChannel<E> =
+ GlobalScope.broadcast(Dispatchers.Unconfined, capacity = capacity, start = start, onCompletion = consumes()) {
+ for (e in this@broadcast) {
+ send(e)
+ }
+ }
+
+/**
+ * Launches new coroutine to produce a stream of values by sending them to a broadcast channel
+ * and returns a reference to the coroutine as a [BroadcastChannel]. The resulting
+ * object can be used to [subscribe][BroadcastChannel.openSubscription] to elements produced by this coroutine.
+ *
+ * The scope of the coroutine contains [ProducerScope] interface, which implements
+ * both [CoroutineScope] and [SendChannel], so that coroutine can invoke
+ * [send][SendChannel.send] directly. The channel is [closed][SendChannel.close]
+ * when the coroutine completes.
+ *
+ * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
+ * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
+ * with corresponding [coroutineContext] element.
+ *
+ * Uncaught exceptions in this coroutine close the channel with this exception as a cause and
+ * the resulting channel becomes _failed_, so that any attempt to receive from such a channel throws exception.
+ *
+ * The kind of the resulting channel depends on the specified [capacity] parameter:
+ * * when `capacity` is positive (1 by default), but less than [UNLIMITED] -- uses `ArrayBroadcastChannel` with a buffer of given capacity,
+ * * when `capacity` is [CONFLATED] -- uses [ConflatedBroadcastChannel] that conflates back-to-back sends;
+ * Note that resulting channel behaves like [ConflatedBroadcastChannel] but is not an instance of [ConflatedBroadcastChannel].
+ * * otherwise -- throws [IllegalArgumentException].
+ *
+ * **Note:** By default, the coroutine does not start until the first subscriber appears via [BroadcastChannel.openSubscription]
+ * as [start] parameter has a value of [CoroutineStart.LAZY] by default.
+ * This ensures that the first subscriber does not miss any sent elements.
+ * However, later subscribers may miss elements.
+ *
+ * See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
+ *
+ * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
+ * @param capacity capacity of the channel's buffer (1 by default).
+ * @param start coroutine start option. The default value is [CoroutineStart.LAZY].
+ * @param onCompletion optional completion handler for the producer coroutine (see [Job.invokeOnCompletion]).
+ * @param block the coroutine code.
+ */
+public fun <E> CoroutineScope.broadcast(
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = 1,
+ start: CoroutineStart = CoroutineStart.LAZY,
+ onCompletion: CompletionHandler? = null,
+ @BuilderInference block: suspend ProducerScope<E>.() -> Unit
+): BroadcastChannel<E> {
+ val newContext = newCoroutineContext(context)
+ val channel = BroadcastChannel<E>(capacity)
+ val coroutine = if (start.isLazy)
+ LazyBroadcastCoroutine(newContext, channel, block) else
+ BroadcastCoroutine(newContext, channel, active = true)
+ if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion)
+ coroutine.start(start, coroutine, block)
+ return coroutine
+}
+
+private open class BroadcastCoroutine<E>(
+ parentContext: CoroutineContext,
+ protected val _channel: BroadcastChannel<E>,
+ active: Boolean
+) : AbstractCoroutine<Unit>(parentContext, active), ProducerScope<E>, BroadcastChannel<E> by _channel {
+ override val isActive: Boolean get() = super.isActive
+
+ override val channel: SendChannel<E>
+ get() = this
+
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ final override fun cancel(cause: Throwable?): Boolean =
+ cancelInternal(cause)
+
+ final override fun cancel(cause: CancellationException?) {
+ cancelInternal(cause)
+ }
+
+ override fun cancelInternal(cause: Throwable?): Boolean {
+ _channel.cancel(cause?.toCancellationException()) // cancel the channel
+ cancelCoroutine(cause) // cancel the job
+ return true // does not matter - result is used in DEPRECATED functions only
+ }
+
+ override fun onCompleted(value: Unit) {
+ _channel.close()
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ val processed = _channel.close(cause)
+ if (!processed && !handled) handleCoroutineException(context, cause)
+ }
+}
+
+private class LazyBroadcastCoroutine<E>(
+ parentContext: CoroutineContext,
+ channel: BroadcastChannel<E>,
+ block: suspend ProducerScope<E>.() -> Unit
+) : BroadcastCoroutine<E>(parentContext, channel, active = false) {
+ private var block: (suspend ProducerScope<E>.() -> Unit)? = block
+
+ override fun openSubscription(): ReceiveChannel<E> {
+ // open subscription _first_
+ val subscription = _channel.openSubscription()
+ // then start coroutine
+ start()
+ return subscription
+ }
+
+ override fun onStart() {
+ val block = checkNotNull(this.block) { "Already started" }
+ this.block = null
+ block.startCoroutineCancellable(this, this)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt
new file mode 100644
index 00000000..2981d839
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("FunctionName")
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
+import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
+import kotlinx.coroutines.channels.Channel.Factory.CHANNEL_DEFAULT_CAPACITY
+import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
+
+/**
+ * Broadcast channel is a non-blocking primitive for communication between the sender and multiple receivers
+ * that subscribe for the elements using [openSubscription] function and unsubscribe using [ReceiveChannel.cancel]
+ * function.
+ *
+ * See `BroadcastChannel()` factory function for the description of available
+ * broadcast channel implementations.
+ *
+ * **Note: This is an experimental api.** It may be changed in the future updates.
+ */
+@ExperimentalCoroutinesApi
+public interface BroadcastChannel<E> : SendChannel<E> {
+ /**
+ * Subscribes to this [BroadcastChannel] and returns a channel to receive elements from it.
+ * The resulting channel shall be [cancelled][ReceiveChannel.cancel] to unsubscribe from this
+ * broadcast channel.
+ */
+ public fun openSubscription(): ReceiveChannel<E>
+
+ /**
+ * Cancels reception of remaining elements from this channel with an optional cause.
+ * This function closes the channel with
+ * the specified cause (unless it was already closed), removes all buffered sent elements from it,
+ * and [cancels][ReceiveChannel.cancel] all open subscriptions.
+ * A cause can be used to specify an error message or to provide other details on
+ * a cancellation reason for debugging purposes.
+ */
+ public fun cancel(cause: CancellationException? = null)
+
+ /**
+ * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel].
+ */
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Binary compatibility only")
+ public fun cancel(cause: Throwable? = null): Boolean
+}
+
+/**
+ * Creates a broadcast channel with the specified buffer capacity.
+ *
+ * The resulting channel type depends on the specified [capacity] parameter:
+ *
+ * * when `capacity` positive, but less than [UNLIMITED] -- creates `ArrayBroadcastChannel` with a buffer of given capacity.
+ * **Note:** this channel looses all items that are send to it until the first subscriber appears;
+ * * when `capacity` is [CONFLATED] -- creates [ConflatedBroadcastChannel] that conflates back-to-back sends;
+ * * 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.
+ */
+@ExperimentalCoroutinesApi
+public fun <E> BroadcastChannel(capacity: Int): BroadcastChannel<E> =
+ when (capacity) {
+ 0 -> throw IllegalArgumentException("Unsupported 0 capacity for BroadcastChannel")
+ UNLIMITED -> throw IllegalArgumentException("Unsupported UNLIMITED capacity for BroadcastChannel")
+ CONFLATED -> ConflatedBroadcastChannel()
+ BUFFERED -> ArrayBroadcastChannel(CHANNEL_DEFAULT_CAPACITY)
+ else -> ArrayBroadcastChannel(capacity)
+ }
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
new file mode 100644
index 00000000..f13a15c2
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -0,0 +1,587 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("FunctionName")
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
+import kotlinx.coroutines.channels.Channel.Factory.RENDEZVOUS
+import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
+import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
+import kotlinx.coroutines.channels.Channel.Factory.CHANNEL_DEFAULT_CAPACITY
+import kotlinx.coroutines.internal.systemProp
+import kotlinx.coroutines.selects.*
+import kotlin.jvm.*
+import kotlin.internal.*
+
+/**
+ * Sender's interface to [Channel].
+ */
+public interface SendChannel<in E> {
+ /**
+ * Returns `true` if this channel was closed by invocation of [close] and thus
+ * the [send] and [offer] attempts throws exception.
+ *
+ * **Note: This is an experimental api.** This property may change its semantics and/or name in the future.
+ */
+ @ExperimentalCoroutinesApi
+ public val isClosedForSend: Boolean
+
+ /**
+ * Returns `true` if the channel is full (out of capacity) and the [send] attempt will suspend.
+ * This function returns `false` for [isClosedForSend] channel.
+ *
+ * @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
+
+ /**
+ * Adds [element] into to this channel, suspending the caller while the buffer of this channel is full
+ * or if it does not exist, or throws exception if the channel [isClosedForSend] (see [close] for details).
+ *
+ * Note that closing a channel _after_ this function had suspended does not cause this suspended send invocation
+ * to abort, because closing a channel is conceptually like sending a special "close token" over this channel.
+ * All elements that are sent over the channel are delivered in first-in first-out order. The element that
+ * is being sent will get delivered to receivers before a close token.
+ *
+ * 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].
+ *
+ * *Cancellation of suspended send is atomic* -- when this function
+ * throws [CancellationException] it means that the [element] was not sent to this channel.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this send operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * 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] invocation with [onSend] clause.
+ * Use [offer] to try sending to this channel without waiting.
+ */
+ public suspend fun send(element: E)
+
+ /**
+ * Clause for [select] expression of [send] suspending function that selects when the element that is specified
+ * as parameter is sent to the channel. When the clause is selected the reference to this channel
+ * is passed into the corresponding block.
+ *
+ * The [select] invocation fails with exception if the channel [isClosedForSend] (see [close] for details).
+ */
+ public val onSend: SelectClause2<E, SendChannel<E>>
+
+ /**
+ * Adds [element] into this queue if it is possible to do so immediately without violating capacity restrictions
+ * and returns `true`. Otherwise, it returns `false` immediately
+ * or throws exception if the channel [isClosedForSend] (see [close] for details).
+ */
+ public fun offer(element: E): Boolean
+
+ /**
+ * Closes this channel.
+ * This is an idempotent operation -- subsequent invocations of this function have no effect and return `false`.
+ * Conceptually, its 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 [ClosedSendChannelException] on attempts to send
+ * and [ClosedReceiveChannelException] on attempts to 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.
+ */
+ public fun close(cause: Throwable? = null): Boolean
+
+ /**
+ * Registers handler which is synchronously invoked once the channel is [closed][close]
+ * or receiving side of this channel is [cancelled][ReceiveChannel.cancel].
+ * Only one handler can be attached to the channel during channel's lifetime.
+ * Handler is invoked when [isClosedForSend] starts to return `true`.
+ * If channel is already closed, handler is invoked immediately.
+ *
+ * The meaning of `cause` that is passed to the handler:
+ * * `null` if channel was closed or cancelled without corresponding argument
+ * * close or cancel cause otherwise.
+ *
+ * Example of usage (exception handling is omitted):
+ * ```
+ * val events = Channel(UNLIMITED)
+ * callbackBasedApi.registerCallback { event ->
+ * events.offer(event)
+ * }
+ *
+ * val uiUpdater = launch(Dispatchers.Main, parent = UILifecycle) {
+ * events.consume {}
+ * events.cancel()
+ * }
+ *
+ * events.invokeOnClose { callbackBasedApi.stop() }
+ *
+ * ```
+ *
+ * **Note: This is an experimental api.** This function may change its semantics, parameters or return type in the future.
+ *
+ * @throws UnsupportedOperationException if underlying channel doesn't support [invokeOnClose].
+ * Implementation note: currently, [invokeOnClose] is unsupported only by Rx-like integrations
+ *
+ * @throws IllegalStateException if another handler was already registered
+ */
+ @ExperimentalCoroutinesApi
+ public fun invokeOnClose(handler: (cause: Throwable?) -> Unit)
+}
+
+/**
+ * Receiver's interface to [Channel].
+ */
+public interface ReceiveChannel<out E> {
+ /**
+ * Returns `true` if this channel was closed by invocation of [close][SendChannel.close] on the [SendChannel]
+ * side and all previously sent items were already received, so that the [receive] attempt
+ * throws [ClosedReceiveChannelException]. If the channel was closed because of the exception, it
+ * is considered closed, too, but it is called a _failed_ channel. All suspending attempts to receive
+ * an element from a failed channel throw the original [close][SendChannel.close] cause exception.
+ *
+ * **Note: This is an experimental api.** This property may change its semantics and/or name in the future.
+ */
+ @ExperimentalCoroutinesApi
+ public val isClosedForReceive: Boolean
+
+ /**
+ * Returns `true` if the channel is empty (contains no elements) and the [receive] attempt will suspend.
+ * This function returns `false` for [isClosedForReceive] channel.
+ */
+ @ExperimentalCoroutinesApi
+ public val isEmpty: Boolean
+
+ /**
+ * Retrieves and removes the element from this channel suspending the caller while this channel is empty,
+ * or throws [ClosedReceiveChannelException] if the channel [isClosedForReceive].
+ * If the channel was closed because of the exception, it is called a _failed_ channel and this function
+ * throws the original [close][SendChannel.close] cause exception.
+ *
+ * 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].
+ *
+ * *Cancellation of suspended receive is atomic* -- when this function
+ * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * 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] invocation with [onReceive] clause.
+ * Use [poll] to try receiving from this channel without waiting.
+ */
+ public suspend fun receive(): E
+
+ /**
+ * Clause for [select] expression of [receive] suspending function that selects with the element that
+ * is received from the channel.
+ * The [select] invocation fails with exception if the channel
+ * [isClosedForReceive] (see [close][SendChannel.close] for details).
+ */
+ public val onReceive: SelectClause1<E>
+
+ /**
+ * Retrieves and removes the element from this channel suspending the caller while this channel is empty,
+ * or returns `null` if the channel is [closed][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 [CancellationException].
+ *
+ * *Cancellation of suspended receive is atomic* -- when this function
+ * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * 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] invocation with [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 [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] without 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 the element from this channel suspending the caller while this channel [isEmpty].
+ * This method returns [ValueOrClosed] with a value if element was successfully retrieved from the channel
+ * or [ValueOrClosed] with close cause if channel was closed.
+ *
+ * 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].
+ *
+ * *Cancellation of suspended receive is atomic* -- when this function
+ * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * 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] invocation with [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.
+ */
+ @InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
+ public suspend fun receiveOrClosed(): ValueOrClosed<E>
+
+ /**
+ * Clause for [select] expression of [receiveOrClosed] suspending function that selects with the [ValueOrClosed] with a value
+ * that is received from the channel or selects with [ValueOrClosed] with a close cause if the channel
+ * [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>>
+
+ /**
+ * Retrieves and removes the element from this channel, or returns `null` if this channel is empty
+ * or is [isClosedForReceive] without cause.
+ * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ */
+ public fun poll(): E?
+
+ /**
+ * Returns new iterator to receive elements from this channels using `for` loop.
+ * Iteration completes normally when the channel is [isClosedForReceive] without cause and
+ * throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ */
+ public operator fun iterator(): ChannelIterator<E>
+
+ /**
+ * Cancels reception of remaining elements from this channel with an optional [cause].
+ * This function closes the channel and removes all buffered sent elements from it.
+ *
+ * A cause can be used to specify an error message or to provide other details on
+ * a cancellation reason for debugging purposes.
+ * If the cause is not specified, then an instance of [CancellationException] with a
+ * default message is created to [close][SendChannel.close] the channel.
+ *
+ * Immediately after invocation of this function [isClosedForReceive] and
+ * [isClosedForSend][SendChannel.isClosedForSend]
+ * on the side of [SendChannel] start returning `true`. All attempts to send to this channel
+ * or receive from this channel will throw [CancellationException].
+ */
+ public fun cancel(cause: CancellationException? = null)
+
+ /**
+ * @suppress This method implements old version of JVM ABI. Use [cancel].
+ */
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ public fun cancel() = cancel(null)
+
+ /**
+ * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel].
+ */
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ public fun cancel(cause: Throwable? = null): Boolean
+}
+
+/**
+ * A discriminated union of [ReceiveChannel.receiveOrClosed] result,
+ * that encapsulates either successfully received element of type [T] from the channel or a close cause.
+ *
+ * :todo: Do not make it public before resolving todos in the code of this class.
+ *
+ * @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.
+ */
+@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
+@InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
+public inline class ValueOrClosed<out T>
+internal constructor(private val holder: Any?) {
+ /**
+ * Returns `true` if this instance represents received element.
+ * In this case [isClosed] returns `false`.
+ * todo: it is commented for now, because it is not used
+ */
+ //public val isValue: Boolean get() = holder !is Closed
+
+ /**
+ * Returns `true` if this instance represents close cause.
+ * In this case [isValue] returns `false`.
+ */
+ public val isClosed: Boolean get() = holder is Closed
+
+ /**
+ * Returns received value if this instance represents received value or throws [IllegalStateException] otherwise.
+ *
+ * :todo: Decide if it is needed how it shall be named with relation to [valueOrThrow]:
+ *
+ * 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 consider dropping this version of `value` and rename [valueOrThrow] to simply `value`.
+ */
+ @Suppress("UNCHECKED_CAST")
+ public val value: T
+ get() = if (holder is Closed) error(DEFAULT_CLOSE_MESSAGE) else holder as T
+
+ /**
+ * Returns received value if this element represents 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
+ */
+ @Suppress("UNCHECKED_CAST")
+ public val valueOrNull: T?
+ get() = if (holder is Closed) null else holder as T
+
+ /**
+ * :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 callsite,
+ * so if one is sure that ValueOrClosed is always 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
+ */
+ //@Suppress("UNCHECKED_CAST")
+ //public val valueOrThrow: T
+ // get() = if (holder is Closed) throw holder.exception else holder as T
+
+ /**
+ * Returns close cause of the channel if this instance represents close cause or throws
+ * [IllegalStateException] otherwise.
+ */
+ @Suppress("UNCHECKED_CAST")
+ public val closeCause: Throwable? get() =
+ if (holder is Closed) holder.cause else error("Channel was not closed")
+
+ /**
+ * @suppress
+ */
+ public override fun toString(): String =
+ when (holder) {
+ is Closed -> holder.toString()
+ else -> "Value($holder)"
+ }
+
+ 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)"
+ }
+
+ /**
+ * 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))
+ }
+}
+
+/**
+ * Iterator for [ReceiveChannel]. Instances of this interface are *not thread-safe* and shall not be used
+ * from concurrent coroutines.
+ */
+public interface ChannelIterator<out E> {
+ /**
+ * Returns `true` if the channel has more elements, suspending the caller while this channel is empty,
+ * or returns `false` if the channel [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause.
+ * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ *
+ * This function retrieves and removes the element from this channel for the subsequent invocation
+ * of [next].
+ *
+ * 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].
+ *
+ * *Cancellation of suspended receive is atomic* -- when this function
+ * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * 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.
+ */
+ public suspend operator fun hasNext(): Boolean
+
+ @Deprecated(message = "Since 1.3.0, binary compatibility with versions <= 1.2.x", level = DeprecationLevel.HIDDEN)
+ @Suppress("INAPPLICABLE_JVM_NAME")
+ @JvmName("next")
+ public suspend fun next0(): E {
+ /*
+ * Before 1.3.0 the "next()" could have been used without invoking "hasNext" first and there were code samples
+ * demonstrating this behavior, so we preserve this logic for full binary backwards compatibility with previously
+ * compiled code.
+ */
+ if (!hasNext()) throw ClosedReceiveChannelException(DEFAULT_CLOSE_MESSAGE)
+ return next()
+ }
+
+ /**
+ * Retrieves the element from the current iterator previously removed from the channel by preceding call to [hasNext] or
+ * throws [IllegalStateException] if [hasNext] was not invoked.
+ * [next] should only be used in pair with [hasNext]:
+ * ```
+ * while (iterator.hasNext()) {
+ * val element = iterator.next()
+ * // ... handle element ...
+ * }
+ * ```
+ *
+ * This method throws [ClosedReceiveChannelException] if the channel [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause.
+ * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ */
+ public operator fun next(): E
+}
+
+/**
+ * Channel is a non-blocking primitive for communication between sender using [SendChannel] and receiver using [ReceiveChannel].
+ * Conceptually, a channel is similar to [BlockingQueue][java.util.concurrent.BlockingQueue],
+ * but it has suspending operations instead of blocking ones and it can be closed.
+ *
+ * `Channel(capacity)` factory function is used to create channels of different kind depending on
+ * the value of `capacity` integer:
+ *
+ * * When `capacity` is 0 -- it creates `RendezvousChannel`.
+ * This channel does not have any buffer at all. An element is transferred from sender
+ * to receiver only when [send] and [receive] invocations meet in time (rendezvous), so [send] suspends
+ * until another coroutine invokes [receive] and [receive] suspends until another coroutine invokes [send].
+ *
+ * * When `capacity` is [Channel.UNLIMITED] -- it creates `LinkedListChannel`.
+ * This is a 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`.
+ *
+ * * When `capacity` is [Channel.CONFLATED] -- it creates `ConflatedChannel`.
+ * This channel buffers at most one element and conflates all subsequent `send` and `offer` invocations,
+ * so that the receiver always gets the most recently sent element.
+ * Back-to-send sent elements are _conflated_ -- only the the most recently sent element is received,
+ * while previously sent elements **are lost**.
+ * Sender to this channel never suspends and [offer] always returns `true`.
+ *
+ * * When `capacity` is positive, but less than [UNLIMITED] -- it creates array-based channel with given capacity.
+ * This channel has an array buffer of a fixed `capacity`.
+ * Sender suspends only when buffer is full and receiver suspends only when buffer is empty.
+ */
+public interface Channel<E> : SendChannel<E>, ReceiveChannel<E> {
+ /**
+ * Constants for channel factory function `Channel()`.
+ */
+ public companion object Factory {
+ /**
+ * Requests channel with unlimited capacity buffer in `Channel(...)` factory function
+ */
+ public const val UNLIMITED = Int.MAX_VALUE
+
+ /**
+ * Requests rendezvous channel in `Channel(...)` factory function -- the `RendezvousChannel` gets created.
+ */
+ public const val RENDEZVOUS = 0
+
+ /**
+ * Requests conflated channel in `Channel(...)` factory function -- the `ConflatedChannel` gets created.
+ */
+ public const val CONFLATED = -1
+
+ /**
+ * Requests buffered channel with a default buffer capacity in `Channel(...)` factory function --
+ * the `ArrayChannel` gets created with a default capacity.
+ * This capacity is equal to 64 by default and can be overridden by setting
+ * [DEFAULT_BUFFER_PROPERTY_NAME] on JVM.
+ */
+ public const val BUFFERED = -2
+
+ // only for internal use, cannot be used with Channel(...)
+ internal const val OPTIONAL_CHANNEL = -3
+
+ /**
+ * Name of the property that defines the default channel capacity when
+ * [BUFFERED] is used as parameter in `Channel(...)` factory function.
+ */
+ public const val DEFAULT_BUFFER_PROPERTY_NAME = "kotlinx.coroutines.channels.defaultBuffer"
+
+ internal val CHANNEL_DEFAULT_CAPACITY = systemProp(DEFAULT_BUFFER_PROPERTY_NAME,
+ 64, 1, UNLIMITED - 1
+ )
+ }
+}
+
+/**
+ * Creates a channel with the specified buffer capacity (or without a buffer by default).
+ * See [Channel] interface documentation for details.
+ *
+ * @param capacity either a positive channel capacity or one of the constants defined in [Channel.Factory].
+ * @throws IllegalArgumentException when [capacity] < -2
+ */
+public fun <E> Channel(capacity: Int = RENDEZVOUS): Channel<E> =
+ when (capacity) {
+ RENDEZVOUS -> RendezvousChannel()
+ UNLIMITED -> LinkedListChannel()
+ CONFLATED -> ConflatedChannel()
+ BUFFERED -> ArrayChannel(CHANNEL_DEFAULT_CAPACITY)
+ else -> ArrayChannel(capacity)
+ }
+
+/**
+ * Indicates attempt to [send][SendChannel.send] on [isClosedForSend][SendChannel.isClosedForSend] channel
+ * that was closed without a cause. A _failed_ channel rethrows the original [close][SendChannel.close] cause
+ * exception on send attempts.
+ *
+ * This exception is a subclass of [IllegalStateException] because, conceptually, sender is responsible
+ * for closing the channel and not be trying to send anything after the channel was close. Attempts to
+ * send into the closed channel indicate logical error in the sender's code.
+ */
+public class ClosedSendChannelException(message: String?) : IllegalStateException(message)
+
+/**
+ * Indicates attempt to [receive][ReceiveChannel.receive] on [isClosedForReceive][ReceiveChannel.isClosedForReceive]
+ * channel that was closed without a cause. A _failed_ channel rethrows the original [close][SendChannel.close] cause
+ * exception on receive attempts.
+ *
+ * This exception is a subclass of [NoSuchElementException] to be consistent with plain collections.
+ */
+public class ClosedReceiveChannelException(message: String?) : NoSuchElementException(message) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt
new file mode 100644
index 00000000..dc2cc5b6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016-2018 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.coroutines.*
+
+@Suppress("DEPRECATION")
+internal open class ChannelCoroutine<E>(
+ parentContext: CoroutineContext,
+ protected val _channel: Channel<E>,
+ active: Boolean
+) : AbstractCoroutine<Unit>(parentContext, active), Channel<E> by _channel {
+ val channel: Channel<E> get() = this
+
+ override fun cancel() {
+ cancelInternal(null)
+ }
+
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ final override fun cancel(cause: Throwable?): Boolean =
+ cancelInternal(cause)
+
+ final override fun cancel(cause: CancellationException?) {
+ cancelInternal(cause)
+ }
+
+ override fun cancelInternal(cause: Throwable?): Boolean {
+ val exception = cause?.toCancellationException()
+ ?: JobCancellationException("$classSimpleName was cancelled", null, this)
+ _channel.cancel(exception) // cancel the channel
+ cancelCoroutine(exception) // cancel the job
+ return true // does not matter - result is used in DEPRECATED functions only
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ suspend fun sendFair(element: E) {
+ (_channel as AbstractSendChannel<E>).sendFair(element)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
new file mode 100644
index 00000000..cd37bfbc
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
@@ -0,0 +1,2195 @@
+/*
+ * Copyright 2016-2018 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")
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+internal const val DEFAULT_CLOSE_MESSAGE = "Channel was closed"
+
+
+// -------- Operations on BroadcastChannel --------
+
+/**
+ * Opens subscription to this [BroadcastChannel] and makes sure that the given [block] consumes all elements
+ * from it by always invoking [cancel][ReceiveChannel.cancel] after the execution of the block.
+ *
+ * **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 inline fun <E, R> BroadcastChannel<E>.consume(block: ReceiveChannel<E>.() -> R): R {
+ val channel = openSubscription()
+ try {
+ return channel.block()
+ } finally {
+ channel.cancel()
+ }
+}
+
+/**
+ * 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 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].
+ *
+ * *Cancellation of suspended receive is atomic* -- when this function
+ * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * 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("EXTENSION_SHADOWED_BY_MEMBER")
+@ExperimentalCoroutinesApi // since 1.3.0, tentatively stable in 1.4.0
+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.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) =
+ 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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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_.
+ */
+@ExperimentalCoroutinesApi // since 1.3.0, tentatively graduates in 1.4.0
+public inline fun <E, R> ReceiveChannel<E>.consume(block: ReceiveChannel<E>.() -> R): R {
+ var cause: Throwable? = null
+ try {
+ return block()
+ } catch (e: Throwable) {
+ cause = e
+ throw e
+ } finally {
+ cancelConsumed(cause)
+ }
+}
+
+/**
+ * Performs the given [action] for each received element and [cancels][ReceiveChannel.cancel]
+ * the channel after the execution of the block.
+ * If you need to iterate over the channel without consuming it, a regular `for` loop should be used instead.
+ *
+ * The operation is _terminal_.
+ * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
+ */
+@ExperimentalCoroutinesApi // since 1.3.0, tentatively graduates in 1.4.0
+public suspend inline fun <E> ReceiveChannel<E>.consumeEach(action: (E) -> Unit) =
+ consume {
+ for (e in this) action(e)
+ }
+
+/**
+ * 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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+@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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+public suspend fun <K, V, M : MutableMap<in K, in V>> ReceiveChannel<Pair<K, V>>.toMap(destination: M): M {
+ consumeEach {
+ destination += 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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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].
+ *
+ * **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",
+ level = DeprecationLevel.WARNING
+)
+public suspend inline fun <S, E : S> ReceiveChannel<E>.reduceIndexed(operation: (index: Int, acc: S, E) -> S): S =
+ 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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+public suspend inline fun <E> ReceiveChannel<E>.sumByDouble(selector: (E) -> Double): Double {
+ var sum = 0.0
+ consumeEach {
+ sum += selector(it)
+ }
+ 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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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)
+}
+
+/**
+ * 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",
+ level = DeprecationLevel.WARNING
+)
+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",
+ level = DeprecationLevel.WARNING
+)
+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
new file mode 100644
index 00000000..b2426399
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt
@@ -0,0 +1,294 @@
+/*
+ * 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 kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.selects.*
+import kotlin.jvm.*
+
+/**
+ * Broadcasts the most recently sent element (aka [value]) to all [openSubscription] subscribers.
+ *
+ * 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`.
+ *
+ * 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.
+ *
+ * This implementation is fully lock-free. In this implementation
+ * [opening][openSubscription] and [closing][ReceiveChannel.cancel] subscription takes O(N) time, where N is the
+ * number of subscribers.
+ *
+ * **Note: This API is experimental.** It may be changed in the future updates.
+ */
+@ExperimentalCoroutinesApi
+public class ConflatedBroadcastChannel<E>() : BroadcastChannel<E> {
+ /**
+ * Creates an instance of this class that already holds a value.
+ *
+ * It is as a shortcut to creating an instance with a default constructor and
+ * immediately sending an element: `ConflatedBroadcastChannel().apply { offer(value) }`.
+ */
+ constructor(value: E) : this() {
+ _state.lazySet(State<E>(value, null))
+ }
+
+ private val _state = atomic<Any>(INITIAL_STATE) // State | Closed
+ private val _updating = atomic(0)
+ // State transitions: null -> handler -> HANDLER_INVOKED
+ private val onCloseHandler = atomic<Any?>(null)
+
+ private companion object {
+ @SharedImmutable
+ private val CLOSED = Closed(null)
+ @SharedImmutable
+ private val UNDEFINED = Symbol("UNDEFINED")
+ private val INITIAL_STATE = State<Any?>(UNDEFINED, null)
+ }
+
+ private class State<E>(
+ @JvmField val value: Any?, // UNDEFINED | E
+ @JvmField val subscribers: Array<Subscriber<E>>?
+ )
+
+ private class Closed(@JvmField val closeCause: Throwable?) {
+ val sendException: Throwable get() = closeCause ?: ClosedSendChannelException(DEFAULT_CLOSE_MESSAGE)
+ val valueException: Throwable get() = closeCause ?: IllegalStateException(DEFAULT_CLOSE_MESSAGE)
+ }
+
+ /**
+ * The most recently sent element to this channel.
+ *
+ * Access to this property throws [IllegalStateException] when this class is constructed without
+ * initial value and no value was sent yet or if it was [closed][close] without a cause.
+ * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ */
+ @Suppress("UNCHECKED_CAST")
+ public val value: E get() {
+ _state.loop { state ->
+ when (state) {
+ is Closed -> throw state.valueException
+ is State<*> -> {
+ if (state.value === UNDEFINED) throw IllegalStateException("No value")
+ return state.value as E
+ }
+ else -> error("Invalid state $state")
+ }
+ }
+ }
+
+ /**
+ * The most recently sent element to this channel or `null` when this class is constructed without
+ * initial value and no value was sent yet or if it was [closed][close].
+ */
+ public val valueOrNull: E? get() = when (val state = _state.value) {
+ is Closed -> null
+ is State<*> -> UNDEFINED.unbox(state.value)
+ else -> error("Invalid state $state")
+ }
+
+ 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> {
+ val subscriber = Subscriber(this)
+ _state.loop { state ->
+ when (state) {
+ is Closed -> {
+ subscriber.close(state.closeCause)
+ return subscriber
+ }
+ is State<*> -> {
+ if (state.value !== UNDEFINED)
+ subscriber.offerInternal(state.value as E)
+ val update = State(state.value, addSubscriber((state as State<E>).subscribers, subscriber))
+ if (_state.compareAndSet(state, update))
+ return subscriber
+ }
+ else -> error("Invalid state $state")
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun closeSubscriber(subscriber: Subscriber<E>) {
+ _state.loop { state ->
+ when (state) {
+ is Closed -> return
+ is State<*> -> {
+ val update = State(state.value, removeSubscriber((state as State<E>).subscribers!!, subscriber))
+ if (_state.compareAndSet(state, update))
+ return
+ }
+ else -> error("Invalid state $state")
+ }
+ }
+ }
+
+ private fun addSubscriber(list: Array<Subscriber<E>>?, subscriber: Subscriber<E>): Array<Subscriber<E>> {
+ if (list == null) return Array(1) { subscriber }
+ return list + subscriber
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun removeSubscriber(list: Array<Subscriber<E>>, subscriber: Subscriber<E>): Array<Subscriber<E>>? {
+ val n = list.size
+ val i = list.indexOf(subscriber)
+ assert { i >= 0 }
+ if (n == 1) return null
+ val update = arrayOfNulls<Subscriber<E>>(n - 1)
+ list.copyInto(
+ destination = update,
+ endIndex = i
+ )
+ list.copyInto(
+ destination = update,
+ destinationOffset = i,
+ startIndex = i + 1
+ )
+ return update as Array<Subscriber<E>>
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ public override fun close(cause: Throwable?): Boolean {
+ _state.loop { state ->
+ when (state) {
+ is Closed -> return false
+ is State<*> -> {
+ val update = if (cause == null) CLOSED else Closed(cause)
+ if (_state.compareAndSet(state, update)) {
+ (state as State<E>).subscribers?.forEach { it.close(cause) }
+ invokeOnCloseHandler(cause)
+ return true
+ }
+ }
+ else -> error("Invalid state $state")
+ }
+ }
+ }
+
+ private fun invokeOnCloseHandler(cause: Throwable?) {
+ val handler = onCloseHandler.value
+ if (handler !== null && handler !== HANDLER_INVOKED
+ && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) {
+ @Suppress("UNCHECKED_CAST")
+ (handler as Handler)(cause)
+ }
+ }
+
+ override fun invokeOnClose(handler: Handler) {
+ // Intricate dance for concurrent invokeOnClose and close
+ if (!onCloseHandler.compareAndSet(null, handler)) {
+ val value = onCloseHandler.value
+ if (value === HANDLER_INVOKED) {
+ throw IllegalStateException("Another handler was already registered and successfully invoked")
+ } else {
+ throw IllegalStateException("Another handler was already registered: $value")
+ }
+ } else {
+ val state = _state.value
+ if (state is Closed && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) {
+ (handler)(state.closeCause)
+ }
+ }
+ }
+
+ /**
+ * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel].
+ */
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ public override fun cancel(cause: Throwable?): Boolean = close(cause)
+
+ /**
+ * Cancels this conflated broadcast channel with an optional cause, same as [close].
+ * This function closes the channel with
+ * the specified cause (unless it was already closed),
+ * and [cancels][ReceiveChannel.cancel] all open subscriptions.
+ * A cause can be used to specify an error message or to provide other details on
+ * a cancellation reason for debugging purposes.
+ */
+ public override fun cancel(cause: CancellationException?) {
+ close(cause)
+ }
+
+ /**
+ * Sends the value to all subscribed receives and stores this value as the most recent state for
+ * future subscribers. This implementation never suspends.
+ * It throws exception if the channel [isClosedForSend] (see [close] for details).
+ */
+ public override suspend fun send(element: E) {
+ offerInternal(element)?.let { throw it.sendException }
+ }
+
+ /**
+ * 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).
+ */
+ public override fun offer(element: E): Boolean {
+ offerInternal(element)?.let { throw it.sendException }
+ return true
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun offerInternal(element: E): Closed? {
+ // If some other thread is updating the state in its offer operation we assume that our offer had linearized
+ // before that offer (we lost) and that offer overwrote us and conflated our offer.
+ if (!_updating.compareAndSet(0, 1)) return null
+ try {
+ _state.loop { state ->
+ when (state) {
+ is Closed -> return state
+ is State<*> -> {
+ val update = State(element, (state as State<E>).subscribers)
+ if (_state.compareAndSet(state, update)) {
+ // Note: Using offerInternal here to ignore the case when this subscriber was
+ // already concurrently closed (assume the close had conflated our offer for this
+ // particular subscriber).
+ state.subscribers?.forEach { it.offerInternal(element) }
+ return null
+ }
+ }
+ else -> error("Invalid state $state")
+ }
+ }
+ } finally {
+ _updating.value = 0 // reset the updating flag to zero even when something goes wrong
+ }
+ }
+
+ public override val onSend: SelectClause2<E, SendChannel<E>>
+ get() = object : SelectClause2<E, SendChannel<E>> {
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, param: E, block: suspend (SendChannel<E>) -> R) {
+ registerSelectSend(select, param, block)
+ }
+ }
+
+ private fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend (SendChannel<E>) -> R) {
+ if (!select.trySelect(null)) return
+ offerInternal(element)?.let {
+ select.resumeSelectCancellableWithException(it.sendException)
+ return
+ }
+ block.startCoroutineUnintercepted(receiver = this, completion = select.completion)
+ }
+
+ private class Subscriber<E>(
+ private val broadcastChannel: ConflatedBroadcastChannel<E>
+ ) : ConflatedChannel<E>(), ReceiveChannel<E> {
+ override fun cancelInternal(cause: Throwable?): Boolean =
+ close(cause).also { closed ->
+ if (closed) broadcastChannel.closeSubscriber(this)
+ }
+
+ public override fun offerInternal(element: E): Any = super.offerInternal(element)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
new file mode 100644
index 00000000..21a18832
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.selects.*
+import kotlinx.coroutines.internal.*
+
+/**
+ * Channel that buffers at most one element and conflates all subsequent `send` and `offer` invocations,
+ * so that the receiver always gets the most recently sent element.
+ * Back-to-send sent elements are _conflated_ -- only the the most recently sent element is received,
+ * while previously sent elements **are lost**.
+ * Sender to this channel never suspends and [offer] always returns `true`.
+ *
+ * This channel is created by `Channel(Channel.CONFLATED)` factory function invocation.
+ *
+ * This implementation is fully lock-free.
+ */
+internal open class ConflatedChannel<E> : AbstractChannel<E>() {
+ protected final override val isBufferAlwaysEmpty: Boolean get() = true
+ protected final override val isBufferEmpty: Boolean get() = true
+ protected final override val isBufferAlwaysFull: Boolean get() = false
+ protected final override val isBufferFull: Boolean get() = false
+
+ override fun onClosedIdempotent(closed: LockFreeLinkedListNode) {
+ @Suppress("UNCHECKED_CAST")
+ (closed.prevNode as? SendBuffered<E>)?.let { lastBuffered ->
+ conflatePreviousSendBuffered(lastBuffered)
+ }
+ }
+
+ /**
+ * Queues conflated element, returns null on success or
+ * returns node reference if it was already closed or is waiting for receive.
+ */
+ private fun sendConflated(element: E): ReceiveOrClosed<*>? {
+ val node = SendBuffered(element)
+ queue.addLastIfPrev(node) { prev ->
+ if (prev is ReceiveOrClosed<*>) return@sendConflated prev
+ true
+ }
+ conflatePreviousSendBuffered(node)
+ return null
+ }
+
+ private fun conflatePreviousSendBuffered(node: SendBuffered<E>) {
+ // Conflate all previous SendBuffered, helping other sends to conflate
+ var prev = node.prevNode
+ while (prev is SendBuffered<*>) {
+ if (!prev.remove()) {
+ prev.helpRemove()
+ }
+ prev = prev.prevNode
+ }
+ }
+
+ // result is always `OFFER_SUCCESS | Closed`
+ protected override fun offerInternal(element: E): Any {
+ while (true) {
+ val result = super.offerInternal(element)
+ when {
+ result === OFFER_SUCCESS -> return OFFER_SUCCESS
+ result === OFFER_FAILED -> { // try to buffer
+ when (val sendResult = sendConflated(element)) {
+ null -> return OFFER_SUCCESS
+ is Closed<*> -> return sendResult
+ }
+ // otherwise there was receiver in queue, retry super.offerInternal
+ }
+ result is Closed<*> -> return result
+ else -> error("Invalid offerInternal result $result")
+ }
+ }
+ }
+
+ // result is always `ALREADY_SELECTED | OFFER_SUCCESS | Closed`.
+ protected override fun offerSelectInternal(element: E, select: SelectInstance<*>): Any {
+ while (true) {
+ val result = if (hasReceiveOrClosed)
+ super.offerSelectInternal(element, select) else
+ (select.performAtomicTrySelect(describeSendConflated(element)) ?: OFFER_SUCCESS)
+ when {
+ result === ALREADY_SELECTED -> return ALREADY_SELECTED
+ result === OFFER_SUCCESS -> return OFFER_SUCCESS
+ result === OFFER_FAILED -> {} // retry
+ result is Closed<*> -> return result
+ else -> error("Invalid result $result")
+ }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt
new file mode 100644
index 00000000..3afc86c4
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.channels
+
+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`.
+ *
+ * This channel is created by `Channel(Channel.UNLIMITED)` factory function invocation.
+ *
+ * This implementation is fully lock-free.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+internal open class LinkedListChannel<E> : AbstractChannel<E>() {
+ protected final override val isBufferAlwaysEmpty: Boolean get() = true
+ protected final override val isBufferEmpty: Boolean get() = true
+ protected final override val isBufferAlwaysFull: Boolean get() = false
+ protected final override val isBufferFull: Boolean get() = false
+
+ // result is always `OFFER_SUCCESS | Closed`
+ protected override fun offerInternal(element: E): Any {
+ while (true) {
+ val result = super.offerInternal(element)
+ when {
+ result === OFFER_SUCCESS -> return OFFER_SUCCESS
+ result === OFFER_FAILED -> { // try to buffer
+ val sendResult = sendBuffered(element)
+ when (sendResult) {
+ null -> return OFFER_SUCCESS
+ is Closed<*> -> return sendResult
+ }
+ // otherwise there was receiver in queue, retry super.offerInternal
+ }
+ result is Closed<*> -> return result
+ else -> error("Invalid offerInternal result $result")
+ }
+ }
+ }
+
+ // result is always `ALREADY_SELECTED | OFFER_SUCCESS | Closed`.
+ protected override fun offerSelectInternal(element: E, select: SelectInstance<*>): Any {
+ while (true) {
+ val result = if (hasReceiveOrClosed)
+ super.offerSelectInternal(element, select) else
+ (select.performAtomicTrySelect(describeSendBuffered(element)) ?: OFFER_SUCCESS)
+ when {
+ result === ALREADY_SELECTED -> return ALREADY_SELECTED
+ result === OFFER_SUCCESS -> return OFFER_SUCCESS
+ result === OFFER_FAILED -> {} // retry
+ result is Closed<*> -> return result
+ else -> error("Invalid result $result")
+ }
+ }
+ }
+}
+
diff --git a/kotlinx-coroutines-core/common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt
new file mode 100644
index 00000000..a579d7a2
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016-2018 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.coroutines.*
+
+/**
+ * Scope for [produce][CoroutineScope.produce] coroutine builder.
+ *
+ * **Note: This is an experimental api.** Behaviour of producers that work as children in a parent scope with respect
+ * to cancellation and error handling may change in the future.
+ */
+@ExperimentalCoroutinesApi
+public interface ProducerScope<in E> : CoroutineScope, SendChannel<E> {
+ /**
+ * A reference to the channel that this coroutine [sends][send] elements to.
+ * It is provided for convenience, so that the code in the coroutine can refer
+ * to the channel as `channel` as apposed to `this`.
+ * All the [SendChannel] functions on this interface delegate to
+ * the channel instance returned by this function.
+ */
+ val channel: SendChannel<E>
+}
+
+/**
+ * Suspends the current coroutine until the channel is either [closed][SendChannel.close] or [cancelled][ReceiveChannel.cancel]
+ * and invokes the given [block] before resuming the coroutine.
+ *
+ * Note that when producer channel is cancelled this function resumes with cancellation exception,
+ * so putting the code after calling this function would not lead to its execution in case of cancellation.
+ * That is why this code takes a lambda parameter.
+ *
+ * Example of usage:
+ * ```
+ * val callbackEventsStream = produce {
+ * val disposable = registerChannelInCallback(channel)
+ * awaitClose { disposable.dispose() }
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
+ check(kotlin.coroutines.coroutineContext[Job] === this) { "awaitClose() can be invoke only from the producer context" }
+ try {
+ suspendCancellableCoroutine<Unit> { cont ->
+ invokeOnClose {
+ cont.resume(Unit)
+ }
+ }
+ } finally {
+ block()
+ }
+}
+
+/**
+ * Launches new coroutine to produce a stream of values by sending them to a channel
+ * and returns a reference to the coroutine as a [ReceiveChannel]. This resulting
+ * object can be used to [receive][ReceiveChannel.receive] elements produced by this coroutine.
+ *
+ * The scope of the coroutine contains [ProducerScope] interface, which implements
+ * both [CoroutineScope] and [SendChannel], so that coroutine can invoke
+ * [send][SendChannel.send] directly. The channel is [closed][SendChannel.close]
+ * when the coroutine completes.
+ * The running coroutine is cancelled when its receive channel is [cancelled][ReceiveChannel.cancel].
+ *
+ * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
+ * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
+ * with corresponding [coroutineContext] element.
+ *
+ * Uncaught exceptions in this coroutine close the channel with this exception as a cause and
+ * the resulting channel becomes _failed_, so that any attempt to receive from such a channel throws exception.
+ *
+ * The kind of the resulting channel depends on the specified [capacity] parameter.
+ * See [Channel] interface documentation for details.
+ *
+ * See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
+ *
+ * **Note: This is an experimental api.** Behaviour of producers that work as children in a parent scope with respect
+ * to cancellation and error handling may change in the future.
+ *
+ * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
+ * @param capacity capacity of the channel's buffer (no buffer by default).
+ * @param block the coroutine code.
+ */
+@ExperimentalCoroutinesApi
+public fun <E> CoroutineScope.produce(
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = 0,
+ @BuilderInference block: suspend ProducerScope<E>.() -> Unit
+): ReceiveChannel<E> {
+ val channel = Channel<E>(capacity)
+ val newContext = newCoroutineContext(context)
+ val coroutine = ProducerCoroutine(newContext, channel)
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+ return coroutine
+}
+
+/**
+ * This an internal API and should not be used from general code.**
+ * onCompletion parameter will be redesigned.
+ * If you have to use `onCompletion` operator, please report to https://github.com/Kotlin/kotlinx.coroutines/issues/.
+ * As a temporary solution, [invokeOnCompletion][Job.invokeOnCompletion] can be used instead:
+ * ```
+ * fun <E> ReceiveChannel<E>.myOperator(): ReceiveChannel<E> = GlobalScope.produce(Dispatchers.Unconfined) {
+ * coroutineContext[Job]?.invokeOnCompletion { consumes() }
+ * }
+ * ```
+ * @suppress
+ */
+@InternalCoroutinesApi
+public fun <E> CoroutineScope.produce(
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = 0,
+ onCompletion: CompletionHandler? = null,
+ @BuilderInference block: suspend ProducerScope<E>.() -> Unit
+): ReceiveChannel<E> {
+ val channel = Channel<E>(capacity)
+ val newContext = newCoroutineContext(context)
+ val coroutine = ProducerCoroutine(newContext, channel)
+ if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion)
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+ return coroutine
+}
+
+internal open class ProducerCoroutine<E>(
+ parentContext: CoroutineContext, channel: Channel<E>
+) : ChannelCoroutine<E>(parentContext, channel, active = true), ProducerScope<E> {
+ override val isActive: Boolean
+ get() = super.isActive
+
+ override fun onCompleted(value: Unit) {
+ _channel.close()
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ val processed = _channel.close(cause)
+ if (!processed && !handled) handleCoroutineException(context, cause)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt b/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt
new file mode 100644
index 00000000..98df1769
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.channels
+
+/**
+ * Rendezvous channel. This channel does not have any buffer at all. An element is transferred from sender
+ * to receiver only when [send] and [receive] invocations meet in time (rendezvous), so [send] suspends
+ * until another coroutine invokes [receive] and [receive] suspends until another coroutine invokes [send].
+ *
+ * Use `Channel()` factory function to conveniently create an instance of rendezvous channel.
+ *
+ * This implementation is fully lock-free.
+ **/
+internal open class RendezvousChannel<E> : AbstractChannel<E>() {
+ protected final override val isBufferAlwaysEmpty: Boolean get() = true
+ protected final override val isBufferEmpty: Boolean get() = true
+ protected final override val isBufferAlwaysFull: Boolean get() = true
+ protected final override val isBufferFull: Boolean get() = true
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/Builders.kt b/kotlinx-coroutines-core/common/src/flow/Builders.kt
new file mode 100644
index 00000000..2b03e331
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/Builders.kt
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
+import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Creates a flow from the given suspendable [block].
+ *
+ * Example of usage:
+ * ```
+ * fun fibonacci(): Flow<Long> = flow {
+ * emit(1L)
+ * var f1 = 1L
+ * var f2 = 1L
+ * repeat(100) {
+ * var tmp = f1
+ * f1 = f2
+ * f2 += tmp
+ * emit(f1)
+ * }
+ * }
+ * ```
+ *
+ * `emit` should happen strictly in the dispatchers of the [block] in order to preserve the flow context.
+ * For example, the following code will result in an [IllegalStateException]:
+ * ```
+ * flow {
+ * emit(1) // Ok
+ * withContext(Dispatcher.IO) {
+ * emit(2) // Will fail with ISE
+ * }
+ * }
+ * ```
+ * If you want to switch the context of execution of a flow, use the [flowOn] operator.
+ */
+public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
+
+// Named anonymous object
+private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : Flow<T> {
+ override suspend fun collect(collector: FlowCollector<T>) {
+ SafeCollector(collector, coroutineContext).block()
+ }
+}
+
+/**
+ * Creates a flow that produces a single value from the given functional type.
+ */
+@FlowPreview
+public fun <T> (() -> T).asFlow(): Flow<T> = flow {
+ emit(invoke())
+}
+
+/**
+ * Creates a flow that produces a single value from the given functional type.
+ * Example of usage:
+ * ```
+ * suspend fun remoteCall(): R = ...
+ * fun remoteCallFlow(): Flow<R> = ::remoteCall.asFlow()
+ * ```
+ */
+@FlowPreview
+public fun <T> (suspend () -> T).asFlow(): Flow<T> = flow {
+ emit(invoke())
+}
+
+/**
+ * Creates a flow that produces values from the given iterable.
+ */
+public fun <T> Iterable<T>.asFlow(): Flow<T> = flow {
+ forEach { value ->
+ emit(value)
+ }
+}
+
+/**
+ * Creates a flow that produces values from the given iterable.
+ */
+public fun <T> Iterator<T>.asFlow(): Flow<T> = flow {
+ forEach { value ->
+ emit(value)
+ }
+}
+
+/**
+ * Creates a flow that produces values from the given sequence.
+ */
+public fun <T> Sequence<T>.asFlow(): Flow<T> = flow {
+ forEach { value ->
+ emit(value)
+ }
+}
+
+/**
+ * Creates a flow that produces values from the given array of elements.
+ */
+public fun <T> flowOf(vararg elements: T): Flow<T> = flow {
+ for (element in elements) {
+ emit(element)
+ }
+}
+
+/**
+ * Creates flow that produces a given [value].
+ */
+public fun <T> flowOf(value: T): Flow<T> = flow {
+ /*
+ * Implementation note: this is just an "optimized" overload of flowOf(vararg)
+ * which significantly reduce the footprint of widespread single-value flows.
+ */
+ emit(value)
+}
+
+/**
+ * Returns an empty flow.
+ */
+public fun <T> emptyFlow(): Flow<T> = EmptyFlow
+
+private object EmptyFlow : Flow<Nothing> {
+ override suspend fun collect(collector: FlowCollector<Nothing>) = Unit
+}
+
+/**
+ * Creates a flow that produces values from the given array.
+ */
+public fun <T> Array<T>.asFlow(): Flow<T> = flow {
+ forEach { value ->
+ emit(value)
+ }
+}
+
+/**
+ * Creates flow that produces values from the given array.
+ */
+public fun IntArray.asFlow(): Flow<Int> = flow {
+ forEach { value ->
+ emit(value)
+ }
+}
+
+/**
+ * Creates flow that produces values from the given array.
+ */
+public fun LongArray.asFlow(): Flow<Long> = flow {
+ forEach { value ->
+ emit(value)
+ }
+}
+
+/**
+ * Creates flow that produces values from the given range.
+ */
+public fun IntRange.asFlow(): Flow<Int> = flow {
+ forEach { value ->
+ emit(value)
+ }
+}
+
+/**
+ * Creates flow that produces values from the given range.
+ */
+public fun LongRange.asFlow(): Flow<Long> = flow {
+ forEach { value ->
+ emit(value)
+ }
+}
+
+/**
+ * @suppress
+ */
+@FlowPreview
+@Deprecated(
+ message = "Use channelFlow with awaitClose { } instead of flowViaChannel and invokeOnClose { }.",
+ level = DeprecationLevel.WARNING
+)
+@Suppress("DeprecatedCallableAddReplaceWith")
+public fun <T> flowViaChannel(
+ bufferSize: Int = BUFFERED,
+ @BuilderInference block: CoroutineScope.(channel: SendChannel<T>) -> Unit
+): Flow<T> {
+ return channelFlow<T> {
+ block(channel)
+ awaitClose()
+ }.buffer(bufferSize)
+}
+
+/**
+ * Creates an instance of the cold [Flow] with elements that are sent to a [SendChannel]
+ * that is provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
+ * produced by the code that is running in a different context or running concurrently.
+ * The resulting flow is _cold_, which means that [block] is called on each call of a terminal operator
+ * on the resulting flow.
+ *
+ * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope] can be used
+ * concurrently from different contexts.
+ * The resulting flow completes as soon as the code in the [block] and all its children complete.
+ * Use [awaitClose] as the last statement to keep it running.
+ * For more detailed example please refer to [callbackFlow] documentation.
+ *
+ * A channel with [default][Channel.BUFFERED] buffer size is used. Use [buffer] operator on the
+ * resulting flow 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.
+ *
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] are
+ * always fused so that only one properly configured channel is used for execution.
+ *
+ * Examples of usage:
+ *
+ * ```
+ * fun <T> Flow<T>.merge(other: Flow<T>): Flow<T> = channelFlow {
+ * // collect from one coroutine and send it
+ * launch {
+ * collect { send(it) }
+ * }
+ * // collect and send from this coroutine, too, concurrently
+ * other.collect { send(it) }
+ * }
+ *
+ * fun <T> contextualFlow(): Flow<T> = channelFlow {
+ * // send from one coroutine
+ * launch(Dispatchers.IO) {
+ * send(computeIoValue())
+ * }
+ * // send from another coroutine, concurrently
+ * launch(Dispatchers.Default) {
+ * send(computeCpuValue())
+ * }
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.() -> Unit): Flow<T> =
+ ChannelFlowBuilder(block)
+
+/**
+ * Creates an instance of the cold [Flow] with elements that are sent to a [SendChannel]
+ * that is provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
+ * produced by the code that is running in a different context or running concurrently.
+ *
+ * The resulting flow is _cold_, which means that [block] is called on each call of a terminal operator
+ * on the resulting flow.
+ *
+ * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope] can be used
+ * from any context, e.g. from the callback-based API.
+ * The resulting flow completes as soon as the code in the [block] and all its children complete.
+ * Use [awaitClose] as the last statement to keep it running.
+ * [awaitClose] argument is called when either flow consumer cancels flow collection
+ * or when callback-based API invokes [SendChannel.close] manually.
+ *
+ * A channel with [default][Channel.BUFFERED] buffer size is used. Use [buffer] operator on the
+ * resulting flow 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.
+ *
+ * Adjacent applications of [callbackFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] are
+ * always fused so that only one properly configured channel is used for execution.
+ *
+ * Example of usage:
+ *
+ * ```
+ * fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow {
+ * val callback = object : Callback { // implementation of some callback interface
+ * override fun onNextValue(value: T) {
+ * // Note: offer drops value when buffer is full
+ * // Use either buffer(Channel.CONFLATED) or buffer(Channel.UNLIMITED) to avoid overfill
+ * offer(value)
+ * }
+ * override fun onApiError(cause: Throwable) {
+ * cancel(CancellationException("API Error", cause))
+ * }
+ * override fun onCompleted() = channel.close()
+ * }
+ * api.register(callback)
+ * // Suspend until either onCompleted or external cancellation are invoked
+ * awaitClose { api.unregister(callback) }
+ * }
+ * ```
+ */
+@Suppress("NOTHING_TO_INLINE")
+@ExperimentalCoroutinesApi
+public inline fun <T> callbackFlow(@BuilderInference noinline block: suspend ProducerScope<T>.() -> Unit): Flow<T> =
+ channelFlow(block)
+
+// ChannelFlow implementation that is the first in the chain of flow operations and introduces (builds) a flow
+private class ChannelFlowBuilder<T>(
+ private val block: suspend ProducerScope<T>.() -> Unit,
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = BUFFERED
+) : ChannelFlow<T>(context, capacity) {
+ override fun create(context: CoroutineContext, capacity: Int): ChannelFlow<T> =
+ ChannelFlowBuilder(block, context, capacity)
+
+ override suspend fun collectTo(scope: ProducerScope<T>) =
+ block(scope)
+
+ override fun toString(): String =
+ "block[$block] -> ${super.toString()}"
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt
new file mode 100644
index 00000000..a554a4ad
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Emits all elements from the given [channel] to this flow collector and [cancels][cancel] (consumes)
+ * the channel afterwards. If you need to iterate over the channel without consuming it,
+ * a regular `for` loop should be used instead.
+ *
+ * This function provides a more efficient shorthand for `channel.consumeEach { value -> emit(value) }`.
+ * See [consumeEach][ReceiveChannel.consumeEach].
+ */
+@ExperimentalCoroutinesApi
+public suspend fun <T> FlowCollector<T>.emitAll(channel: ReceiveChannel<T>) {
+ // Manually inlined "consumeEach" implementation that does not use iterator but works via "receiveOrClosed".
+ // 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
+ // See https://github.com/Kotlin/kotlinx.coroutines/issues/1333
+ var cause: Throwable? = null
+ try {
+ while (true) {
+ // :KLUDGE: This "run" call is resolved to an extension function "run" and forces the size of
+ // spilled state to increase by an additional slot, so there are 4 object local variables spilled here
+ // which makes the size of spill state equal to the 4 slots that are spilled around subsequent "emit"
+ // call, ensuring that the previously emitted value is not retained in the state while receiving
+ // the next one.
+ // L$0 <- this
+ // L$1 <- channel
+ // L$2 <- cause
+ // L$3 <- this$run (actually equal to this)
+ val result = run { channel.receiveOrClosed() }
+ if (result.isClosed) {
+ result.closeCause?.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
+ // it is not actually needed in the next loop iteration.
+ // L$0 <- this
+ // L$1 <- channel
+ // L$2 <- cause
+ // L$3 <- result
+ emit(result.value)
+ }
+ } catch (e: Throwable) {
+ cause = e
+ throw e
+ } finally {
+ channel.cancelConsumed(cause)
+ }
+}
+
+/**
+ * Represents the given receive channel as a hot flow and [consumes][ReceiveChannel.consume] the channel
+ * on the first collection from this flow. The resulting flow can be collected just once and throws
+ * [IllegalStateException] when trying to collect it more than once.
+ *
+ * ### Cancellation semantics
+ *
+ * 1) Flow consumer is cancelled when the original channel is cancelled.
+ * 2) Flow consumer completes normally when the original channel completes (~is closed) normally.
+ * 3) If the flow consumer fails with an exception, channel is cancelled.
+ *
+ * ### Operator fusion
+ *
+ * Adjacent applications of [flowOn], [buffer], [conflate], and [produceIn] to the result of `consumeAsFlow` are fused.
+ * In particular, [produceIn] returns the original channel (but throws [IllegalStateException] on repeated calls).
+ * Calls to [flowOn] have generally no effect, unless [buffer] is used to explicitly request buffering.
+ */
+@FlowPreview
+public fun <T> ReceiveChannel<T>.consumeAsFlow(): Flow<T> = ConsumeAsFlow(this)
+
+/**
+ * Represents an existing [channel] as [ChannelFlow] implementation.
+ * It fuses with subsequent [flowOn] operators, but for the most part ignores the specified context.
+ * However, additional [buffer] calls cause a separate buffering channel to be created and that is where
+ * the context might play a role, because it is used by the producing coroutine.
+ */
+private class ConsumeAsFlow<T>(
+ private val channel: ReceiveChannel<T>,
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = Channel.OPTIONAL_CHANNEL
+) : ChannelFlow<T>(context, capacity) {
+ private val consumed = atomic(false)
+
+ private fun markConsumed() =
+ check(!consumed.getAndSet(true)) { "ReceiveChannel.consumeAsFlow can be collected just once" }
+
+ override fun create(context: CoroutineContext, capacity: Int): ChannelFlow<T> =
+ ConsumeAsFlow(channel, context, capacity)
+
+ override suspend fun collectTo(scope: ProducerScope<T>) =
+ SendingCollector(scope).emitAll(channel) // 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) {
+ channel // direct
+ } else
+ super.produceImpl(scope) // extra buffering channel
+ }
+
+ override suspend fun collect(collector: FlowCollector<T>) {
+ if (capacity == Channel.OPTIONAL_CHANNEL) {
+ markConsumed()
+ collector.emitAll(channel) // direct
+ } else {
+ super.collect(collector) // extra buffering channel, produceImpl will mark it as consumed
+ }
+ }
+
+ override fun additionalToStringProps(): String = "channel=$channel, "
+}
+
+/**
+ * Represents the given broadcast channel as a hot flow.
+ * Every flow collector will trigger a new broadcast channel subscription.
+ *
+ * ### Cancellation semantics
+ * 1) Flow consumer is cancelled when the original channel is cancelled.
+ * 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
+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 `produce` 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.
+ */
+@FlowPreview
+public fun <T> Flow<T>.broadcastIn(
+ scope: CoroutineScope,
+ start: CoroutineStart = CoroutineStart.LAZY
+): BroadcastChannel<T> =
+ asChannelFlow().broadcastImpl(scope, start)
+
+/**
+ * Creates a [produce] coroutine that collects the given flow.
+ *
+ * This transformation is **stateful**, it launches a [produce] 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 `produce` 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.
+ */
+@FlowPreview
+public fun <T> Flow<T>.produceIn(
+ scope: CoroutineScope
+): ReceiveChannel<T> =
+ asChannelFlow().produceImpl(scope)
diff --git a/kotlinx-coroutines-core/common/src/flow/Flow.kt b/kotlinx-coroutines-core/common/src/flow/Flow.kt
new file mode 100644
index 00000000..6d87c2b9
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/Flow.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2016-2019 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.flow.internal.SafeCollector
+import kotlin.coroutines.*
+
+/**
+ * A cold asynchronous data stream that sequentially emits values
+ * and completes normally or with an exception.
+ *
+ * _Intermediate operators_ on the flow such as [map], [filter], [take], [zip], etc are functions that are
+ * applied to the _upstream_ flow or flows and return a _downstream_ flow where further operators can be applied to.
+ * Intermediate operations do not execute any code in the flow and are not suspending functions themselves.
+ * They only set up a chain of operations for future execution and quickly return.
+ * This is known as a _cold flow_ property.
+ *
+ * _Terminal operators_ on the flow are either suspending functions such as [collect], [single], [reduce], [toList], etc.
+ * or [launchIn] operator that starts collection of the flow in the given scope.
+ * They are applied to the upstream flow and trigger execution of all operations.
+ * Execution of the flow is also called _collecting the flow_ and is always performed in a suspending manner
+ * without actual blocking. Terminal operators complete normally or exceptionally depending on successful or failed
+ * execution of all the flow operations in the upstream. The most basic terminal operator is [collect], for example:
+ *
+ * ```
+ * try {
+ * flow.collect { value ->
+ * println("Received $value")
+ * }
+ * } catch (e: Exception) {
+ * println("The flow has thrown an exception: $e")
+ * }
+ * ```
+ *
+ * By default, flows are _sequential_ and all flow operations are executed sequentially in the same coroutine,
+ * with an exception for a few operations specifically designed to introduce concurrency into flow
+ * execution such as [buffer] and [flatMapMerge]. See their documentation for details.
+ *
+ * The `Flow` interface does not carry information whether a flow truly is a cold stream that can be collected repeatedly and
+ * triggers execution of the same code every time it is collected, or if it is a hot stream that emits different
+ * values from the same running source on each collection. However, conventionally flows represent cold streams.
+ * Transitions between hot and cold streams are supported via channels and the corresponding API:
+ * [channelFlow], [produceIn], [broadcastIn].
+ *
+ * ### Flow builders
+ *
+ * There are the following basic ways to create a flow:
+ *
+ * * [flowOf(...)][flowOf] functions to create a flow from a fixed set of values.
+ * * [asFlow()][asFlow] extension functions on various types to convert them into flows.
+ * * [flow { ... }][flow] builder function to construct arbitrary flows from
+ * sequential calls to [emit][FlowCollector.emit] function.
+ * * [channelFlow { ... }][channelFlow] builder function to construct arbitrary flows from
+ * potentially concurrent calls to the [send][kotlinx.coroutines.channels.SendChannel.send] function.
+ *
+ * ### Flow constraints
+ *
+ * All implementations of the `Flow` interface must adhere to two key properties described in detail below:
+ *
+ * * Context preservation.
+ * * Exception transparency.
+ *
+ * These properties ensure the ability to perform local reasoning about the code with flows and modularize the code
+ * in such a way that upstream flow emitters can be developed separately from downstream flow collectors.
+ * A user of a flow does not need to be aware of implementation details of the upstream flows it uses.
+ *
+ * ### Context preservation
+ *
+ * The flow has a context preservation property: it encapsulates its own execution context and never propagates or leaks
+ * it downstream, thus making reasoning about the execution context of particular transformations or terminal
+ * operations trivial.
+ *
+ * There is only one way to change the context of a flow: the [flowOn][Flow.flowOn] operator
+ * that changes the upstream context ("everything above the `flowOn` operator").
+ * For additional information refer to its documentation.
+ *
+ * This reasoning can be demonstrated in practice:
+ *
+ * ```
+ * val flowA = flowOf(1, 2, 3)
+ * .map { it + 1 } // Will be executed in ctxA
+ * .flowOn(ctxA) // Changes the upstream context: flowOf and map
+ *
+ * // Now we have a context-preserving flow: it is executed somewhere but this information is encapsulated in the flow itself
+ *
+ * val filtered = flowA // ctxA is encapsulated in flowA
+ * .filter { it == 3 } // Pure operator without a context yet
+ *
+ * withContext(Dispatchers.Main) {
+ * // All non-encapsulated operators will be executed in Main: filter and single
+ * val result = filtered.single()
+ * myUi.text = result
+ * }
+ * ```
+ *
+ * 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.
+ * Its implementation prevents most of the development mistakes:
+ *
+ * ```
+ * val myFlow = flow {
+ * // GlobalScope.launch { // is prohibited
+ * // launch(Dispatchers.IO) { // is prohibited
+ * // withContext(CoroutineName("myFlow")) // is prohibited
+ * emit(1) // OK
+ * coroutineScope {
+ * emit(2) // OK -- still the same coroutine
+ * }
+ * }
+ * ```
+ *
+ * Use [channelFlow] if the collection and emission of a flow are to be separated into multiple coroutines.
+ * It encapsulates all the context preservation work and allows you to focus on your
+ * domain-specific problem, rather than invariant implementation details.
+ * It is possible to use any combination of coroutine builders from within [channelFlow].
+ *
+ * If you are looking for performance and are sure that no concurrent emits and context jumps will happen,
+ * the [flow] builder can be used alongside a [coroutineScope] or [supervisorScope] instead:
+ * - Scoped primitive should be used to provide a [CoroutineScope].
+ * - Changing the context of emission is prohibited, no matter whether it is `withContext(ctx)` or
+ * a builder argument (e.g. `launch(ctx)`).
+ * - Collecting another flow from a separate context is allowed, but it has the same effect as
+ * applying the [flowOn] operator to that flow, which is more efficient.
+ *
+ * ### Exception transparency
+ *
+ * Flow implementations never catch or handle exceptions that occur in downstream flows. From the implementation standpoint
+ * it means that calls to [emit][FlowCollector.emit] and [emitAll] shall never be wrapped into
+ * `try { ... } catch { ... }` blocks. Exception handling in flows shall be performed with
+ * [catch][Flow.catch] operator and it is designed to only catch exceptions coming from upstream flows while passing
+ * all downstream exceptions. Similarly, terminal operators like [collect][Flow.collect]
+ * throw any unhandled exceptions that occur in their code or in upstream flows, for example:
+ *
+ * ```
+ * flow { emitData() }
+ * .map { computeOne(it) }
+ * .catch { ... } // catches exceptions in emitData and computeOne
+ * .map { computeTwo(it) }
+ * .collect { process(it) } // throws exceptions from process and computeTwo
+ * ```
+ * The same reasoning can be applied to the [onCompletion] operator that is a declarative replacement for the `finally` block.
+ *
+ * Failure to adhere to the exception transparency requirement can lead to strange behaviors which make
+ * it hard to reason about the code because an exception in the `collect { ... }` could be somehow "caught"
+ * by an upstream flow, limiting the ability of local reasoning about the code.
+ *
+ * Currently, the flow infrastructure does not enforce exception transparency contracts, however, it might be enforced
+ * in the future either at run time or at compile time.
+ *
+ * ### Reactive streams
+ *
+ * Flow is [Reactive Streams](http://www.reactive-streams.org/) compliant, you can safely interop it with
+ * reactive streams using [Flow.asPublisher] and [Publisher.asFlow] from `kotlinx-coroutines-reactive` module.
+ */
+public interface Flow<out T> {
+ /**
+ * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
+ * This method should never be implemented or used directly.
+ *
+ * The only way to implement the `Flow` interface directly is to extend [AbstractFlow].
+ * To collect it into a specific collector, either `collector.emitAll(flow)` or `collect { ... }` extension
+ * should be used. Such limitation ensures that the context preservation property is not violated and prevents most
+ * of the developer mistakes related to concurrency, inconsistent flow dispatchers and cancellation.
+ */
+ @InternalCoroutinesApi
+ public suspend fun collect(collector: FlowCollector<T>)
+}
+
+/**
+ * Base class for stateful implementations of `Flow`.
+ * It tracks all the properties required for context preservation and throws an [IllegalStateException]
+ * if any of the properties are violated.
+ *
+ * Example of the implementation:
+ *
+ * ```
+ * // list.asFlow() + collect counter
+ * class CountingListFlow(private val values: List<Int>) : AbstractFlow<Int>() {
+ * private val collectedCounter = AtomicInteger(0)
+ *
+ * override suspend fun collectSafely(collector: FlowCollector<Int>) {
+ * collectedCounter.incrementAndGet() // Increment collected counter
+ * values.forEach { // Emit all the values
+ * collector.emit(it)
+ * }
+ * }
+ *
+ * fun toDiagnosticString(): String = "Flow with values $values was collected ${collectedCounter.value} times"
+ * }
+ * ```
+ */
+@FlowPreview
+public abstract class AbstractFlow<T> : Flow<T> {
+
+ @InternalCoroutinesApi
+ public final override suspend fun collect(collector: FlowCollector<T>) {
+ collectSafely(SafeCollector(collector, collectContext = coroutineContext))
+ }
+
+ /**
+ * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
+ *
+ * A valid implementation of this method has the following constraints:
+ * 1) It should not change the coroutine context (e.g. with `withContext(Dispatchers.IO)`) when emitting values.
+ * The emission should happen in the context of the [collect] call.
+ * Please refer to the top-level [Flow] documentation for more details.
+ * 2) It should serialize calls to [emit][FlowCollector.emit] as [FlowCollector] implementations are not
+ * thread-safe by default.
+ * To automatically serialize emissions [channelFlow] builder can be used instead of [flow]
+ *
+ * @throws IllegalStateException if any of the invariants are violated.
+ */
+ public abstract suspend fun collectSafely(collector: FlowCollector<T>)
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
new file mode 100644
index 00000000..7254c6d7
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+/**
+ * [FlowCollector] is used as an intermediate or a terminal collector of the flow and represents
+ * an entity that accepts values emitted by the [Flow].
+ *
+ * This interface should usually not be implemented directly, but rather used as a receiver in a [flow] builder when implementing a custom operator.
+ * Implementations of this interface are not thread-safe.
+ */
+public interface FlowCollector<in T> {
+
+ /**
+ * Collects the value emitted by the upstream.
+ * This method is not thread-safe and should not be invoked concurrently.
+ */
+ public suspend fun emit(value: T)
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/Migration.kt b/kotlinx-coroutines-core/common/src/flow/Migration.kt
new file mode 100644
index 00000000..16769ad8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/Migration.kt
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+@file:Suppress("unused", "DeprecatedCallableAddReplaceWith", "UNUSED_PARAMETER")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.flow.internal.unsafeFlow
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * **GENERAL NOTE**
+ *
+ * These deprecations are added to improve user experience when they will start to
+ * search for their favourite operators and/or patterns that are missing or renamed in Flow.
+ * Deprecated functions also are moved here when they renamed. The difference is that they have
+ * a body with their implementation while pure stubs have [noImpl].
+ */
+private fun noImpl(): Nothing =
+ throw UnsupportedOperationException("Not implemented, should not be called")
+
+/**
+ * `observeOn` has no direct match in [Flow] API because all terminal flow operators are suspending and
+ * thus use the context of the caller.
+ *
+ * For example, the following code:
+ * ```
+ * flowable
+ * .observeOn(Schedulers.io())
+ * .doOnEach { value -> println("Received $value") }
+ * .subscribe()
+ * ```
+ *
+ * has the following Flow equivalent:
+ * ```
+ * withContext(Dispatchers.IO) {
+ * flow.collect { value -> println("Received $value") }
+ * }
+ *
+ * ```
+ * @suppress
+ */
+@Deprecated(message = "Collect flow in the desired context instead", level = DeprecationLevel.ERROR)
+public fun <T> Flow<T>.observeOn(context: CoroutineContext): Flow<T> = noImpl()
+
+/**
+ * `publishOn` has no direct match in [Flow] API because all terminal flow operators are suspending and
+ * thus use the context of the caller.
+ *
+ * For example, the following code:
+ * ```
+ * flux
+ * .publishOn(Schedulers.io())
+ * .doOnEach { value -> println("Received $value") }
+ * .subscribe()
+ * ```
+ *
+ * has the following Flow equivalent:
+ * ```
+ * withContext(Dispatchers.IO) {
+ * flow.collect { value -> println("Received $value") }
+ * }
+ *
+ * ```
+ * @suppress
+ */
+@Deprecated(message = "Collect flow in the desired context instead", level = DeprecationLevel.ERROR)
+public fun <T> Flow<T>.publishOn(context: CoroutineContext): Flow<T> = noImpl()
+
+/**
+ * `subscribeOn` has no direct match in [Flow] API because [Flow] preserves its context and does not leak it.
+ *
+ * For example, the following code:
+ * ```
+ * flowable
+ * .map { value -> println("Doing map in IO"); value }
+ * .subscribeOn(Schedulers.io())
+ * .observeOn(Schedulers.computation())
+ * .doOnEach { value -> println("Processing $value in computation")
+ * .subscribe()
+ * ```
+ * has the following Flow equivalent:
+ * ```
+ * withContext(Dispatchers.Default) {
+ * flow
+ * .map { value -> println("Doing map in IO"); value }
+ * .flowOn(Dispatchers.IO) // Works upstream, doesn't change downstream
+ * .collect { value ->
+ * println("Processing $value in computation")
+ * }
+ * }
+ * ```
+ * Opposed to subscribeOn, it it **possible** to use multiple `flowOn` operators in the one flow
+ * @suppress
+ */
+@Deprecated(message = "Use flowOn instead", level = DeprecationLevel.ERROR)
+public fun <T> Flow<T>.subscribeOn(context: CoroutineContext): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `onErrorXxx` is [catch].
+ * Use `catch { emitAll(fallback) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'onErrorXxx' is 'catch'. Use 'catch { emitAll(fallback) }'",
+ replaceWith = ReplaceWith("catch { emitAll(fallback) }")
+)
+public fun <T> Flow<T>.onErrorResume(fallback: Flow<T>): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `onErrorXxx` is [catch].
+ * Use `catch { emitAll(fallback) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'onErrorXxx' is 'catch'. Use 'catch { emitAll(fallback) }'",
+ replaceWith = ReplaceWith("catch { emitAll(fallback) }")
+)
+public fun <T> Flow<T>.onErrorResumeNext(fallback: Flow<T>): Flow<T> = noImpl()
+
+/**
+ * Self-explanatory, the reason of deprecation is "context preservation" property (you can read more in [Flow] documentation)
+ * @suppress
+ **/
+@Suppress("UNUSED_PARAMETER", "UNUSED", "DeprecatedCallableAddReplaceWith")
+@Deprecated(message = "withContext in flow body is deprecated, use flowOn instead", level = DeprecationLevel.ERROR)
+public fun <T, R> FlowCollector<T>.withContext(context: CoroutineContext, block: suspend () -> R): Unit = noImpl()
+
+/**
+ * `subscribe` is Rx-specific API that has no direct match in flows.
+ * One can use [launchIn] instead, for example the following:
+ * ```
+ * flowable
+ * .observeOn(Schedulers.io())
+ * .subscribe({ println("Received $it") }, { println("Exception $it happened") }, { println("Flowable is completed successfully") }
+ * ```
+ *
+ * has the following Flow equivalent:
+ * ```
+ * flow
+ * .onEach { value -> println("Received $value") }
+ * .onCompletion { cause -> if (cause == null) println("Flow is completed successfully") }
+ * .catch { cause -> println("Exception $cause happened") }
+ * .flowOn(Dispatchers.IO)
+ * .launchIn(myScope)
+ * ```
+ *
+ * Note that resulting value of [launchIn] is not used because the provided scope takes care of cancellation.
+ *
+ * Or terminal operators like [single] can be used from suspend functions.
+ * @suppress
+ */
+@Deprecated(
+ message = "Use launchIn with onEach, onCompletion and catch operators instead",
+ level = DeprecationLevel.ERROR
+)
+public fun <T> Flow<T>.subscribe(): Unit = noImpl()
+
+/**
+ * Use [launchIn] with [onEach], [onCompletion] and [catch] operators instead.
+ * @suppress
+ */
+@Deprecated(
+ message = "Use launchIn with onEach, onCompletion and catch operators instead",
+ level = DeprecationLevel.ERROR
+)public fun <T> Flow<T>.subscribe(onEach: suspend (T) -> Unit): Unit = noImpl()
+
+/**
+ * Use [launchIn] with [onEach], [onCompletion] and [catch] operators instead.
+ * @suppress
+ */
+@Deprecated(
+ message = "Use launchIn with onEach, onCompletion and catch operators instead",
+ level = DeprecationLevel.ERROR
+)public fun <T> Flow<T>.subscribe(onEach: suspend (T) -> Unit, onError: suspend (Throwable) -> Unit): Unit = noImpl()
+
+/**
+ * Note that this replacement is sequential (`concat`) by default.
+ * For concurrent flatMap [flatMapMerge] can be used instead.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue is named flatMapConcat",
+ replaceWith = ReplaceWith("flatMapConcat(mapper)")
+)
+public fun <T, R> Flow<T>.flatMap(mapper: suspend (T) -> Flow<R>): Flow<R> = noImpl()
+
+/**
+ * Flow analogue of `concatMap` is [flatMapConcat].
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'concatMap' is 'flatMapConcat'",
+ replaceWith = ReplaceWith("flatMapConcat(mapper)")
+)
+public fun <T, R> Flow<T>.concatMap(mapper: (T) -> Flow<R>): Flow<R> = noImpl()
+
+/**
+ * Note that this replacement is sequential (`concat`) by default.
+ * For concurrent flatMap [flattenMerge] can be used instead.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'merge' is 'flattenConcat'",
+ replaceWith = ReplaceWith("flattenConcat()")
+)
+public fun <T> Flow<Flow<T>>.merge(): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `flatten` is [flattenConcat].
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'flatten' is 'flattenConcat'",
+ replaceWith = ReplaceWith("flattenConcat()")
+)
+public fun <T> Flow<Flow<T>>.flatten(): Flow<T> = noImpl()
+
+/**
+ * Kotlin has a built-in generic mechanism for making chained calls.
+ * If you wish to write something like
+ * ```
+ * myFlow.compose(MyFlowExtensions.ignoreErrors()).collect { ... }
+ * ```
+ * you can replace it with
+ *
+ * ```
+ * myFlow.let(MyFlowExtensions.ignoreErrors()).collect { ... }
+ * ```
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'compose' is 'let'",
+ replaceWith = ReplaceWith("let(transformer)")
+)
+public fun <T, R> Flow<T>.compose(transformer: Flow<T>.() -> Flow<R>): Flow<R> = noImpl()
+
+/**
+ * Flow analogue of `skip` is [drop].
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'skip' is 'drop'",
+ replaceWith = ReplaceWith("drop(count)")
+)
+public fun <T> Flow<T>.skip(count: Int): Flow<T> = noImpl()
+
+/**
+ * Flow extension to iterate over elements is [collect].
+ * Foreach wasn't introduced deliberately to avoid confusion.
+ * Flow is not a collection, iteration over it may be not idempotent
+ * and can *launch* computations with side-effects.
+ * This behaviour is not reflected in [forEach] name.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'forEach' is 'collect'",
+ replaceWith = ReplaceWith("collect(block)")
+)
+public fun <T> Flow<T>.forEach(action: suspend (value: T) -> Unit): Unit = noImpl()
+
+/**
+ * Flow has less verbose [scan] shortcut.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow has less verbose 'scan' shortcut",
+ replaceWith = ReplaceWith("scan(initial, operation)")
+)
+public fun <T, R> Flow<T>.scanFold(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow<R> =
+ noImpl()
+
+/**
+ * Flow analogue of `onErrorXxx` is [catch].
+ * Use `catch { emit(fallback) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'onErrorXxx' is 'catch'. Use 'catch { emit(fallback) }'",
+ replaceWith = ReplaceWith("catch { emit(fallback) }")
+)
+// Note: this version without predicate gives better "replaceWith" action
+public fun <T> Flow<T>.onErrorReturn(fallback: T): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `onErrorXxx` is [catch].
+ * Use `catch { e -> if (predicate(e)) emit(fallback) else throw e }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'onErrorXxx' is 'catch'. Use 'catch { e -> if (predicate(e)) emit(fallback) else throw e }'",
+ replaceWith = ReplaceWith("catch { e -> if (predicate(e)) emit(fallback) else throw e }")
+)
+public fun <T> Flow<T>.onErrorReturn(fallback: T, predicate: (Throwable) -> Boolean = { true }): Flow<T> =
+ catch { e ->
+ // Note: default value is for binary compatibility with preview version, that is why it has body
+ if (!predicate(e)) throw e
+ emit(fallback)
+ }
+
+/**
+ * Flow analogue of `startWith` is [onStart].
+ * Use `onStart { emit(value) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'startWith' is 'onStart'. Use 'onStart { emit(value) }'",
+ replaceWith = ReplaceWith("onStart { emit(value) }")
+)
+public fun <T> Flow<T>.startWith(value: T): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `startWith` is [onStart].
+ * Use `onStart { emitAll(other) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'startWith' is 'onStart'. Use 'onStart { emitAll(other) }'",
+ replaceWith = ReplaceWith("onStart { emitAll(other) }")
+)
+public fun <T> Flow<T>.startWith(other: Flow<T>): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `concatWith` is [onCompletion].
+ * Use `onCompletion { emit(value) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'concatWith' is 'onCompletion'. Use 'onCompletion { emit(value) }'",
+ replaceWith = ReplaceWith("onCompletion { emit(value) }")
+)
+public fun <T> Flow<T>.concatWith(value: T): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `concatWith` is [onCompletion].
+ * Use `onCompletion { emitAll(other) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'concatWith' is 'onCompletion'. Use 'onCompletion { emitAll(other) }'",
+ replaceWith = ReplaceWith("onCompletion { emitAll(other) }")
+)
+public fun <T> Flow<T>.concatWith(other: Flow<T>): Flow<T> = noImpl()
+
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'combineLatest' is 'combine'",
+ replaceWith = ReplaceWith("this.combine(other, transform)")
+)
+public fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> =
+ combine(this, other, transform)
+
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'combineLatest' is 'combine'",
+ replaceWith = ReplaceWith("combine(this, other, other2, transform)")
+)
+public inline fun <T1, T2, T3, R> Flow<T1>.combineLatest(
+ other: Flow<T2>,
+ other2: Flow<T3>,
+ crossinline transform: suspend (T1, T2, T3) -> R
+) = combine(this, other, other2, transform)
+
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'combineLatest' is 'combine'",
+ replaceWith = ReplaceWith("combine(this, other, other2, other3, transform)")
+)
+public inline fun <T1, T2, T3, T4, R> Flow<T1>.combineLatest(
+ other: Flow<T2>,
+ other2: Flow<T3>,
+ other3: Flow<T4>,
+ crossinline transform: suspend (T1, T2, T3, T4) -> R
+) = combine(this, other, other2, other3, transform)
+
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'combineLatest' is 'combine'",
+ replaceWith = ReplaceWith("combine(this, other, other2, other3, transform)")
+)
+public inline fun <T1, T2, T3, T4, T5, R> Flow<T1>.combineLatest(
+ other: Flow<T2>,
+ other2: Flow<T3>,
+ other3: Flow<T4>,
+ other4: Flow<T5>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5) -> R
+): Flow<R> = combine(this, other, other2, other3, other4, transform)
+
+/**
+ * Delays the emission of values from this flow for the given [timeMillis].
+ * Use `onStart { delay(timeMillis) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.WARNING, // since 1.3.0, error in 1.4.0
+ message = "Use 'onStart { delay(timeMillis) }'",
+ replaceWith = ReplaceWith("onStart { delay(timeMillis) }")
+)
+public fun <T> Flow<T>.delayFlow(timeMillis: Long): Flow<T> = onStart { delay(timeMillis) }
+
+/**
+ * Delays each element emitted by the given flow for the given [timeMillis].
+ * Use `onEach { delay(timeMillis) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.WARNING, // since 1.3.0, error in 1.4.0
+ message = "Use 'onEach { delay(timeMillis) }'",
+ replaceWith = ReplaceWith("onEach { delay(timeMillis) }")
+)
+public fun <T> Flow<T>.delayEach(timeMillis: Long): Flow<T> = onEach { delay(timeMillis) }
+
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogues of 'switchMap' are 'transformLatest', 'flatMapLatest' and 'mapLatest'",
+ replaceWith = ReplaceWith("this.flatMapLatest(transform)")
+)
+public fun <T, R> Flow<T>.switchMap(transform: suspend (value: T) -> Flow<R>): Flow<R> = flatMapLatest(transform)
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt
new file mode 100644
index 00000000..4711b884
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.jvm.*
+
+internal fun <T> Flow<T>.asChannelFlow(): ChannelFlow<T> =
+ this as? ChannelFlow ?: ChannelFlowOperatorImpl(this)
+
+/**
+ * Operators that use channels extend this ChannelFlow and are always fused with each other.
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public abstract class ChannelFlow<T>(
+ // upstream context
+ @JvmField val context: CoroutineContext,
+ // buffer capacity between upstream and downstream context
+ @JvmField val capacity: Int
+) : Flow<T> {
+ public fun update(
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = Channel.OPTIONAL_CHANNEL
+ ): ChannelFlow<T> {
+ // note: previous upstream context (specified before) takes precedence
+ val newContext = context + this.context
+ val newCapacity = when {
+ this.capacity == Channel.OPTIONAL_CHANNEL -> capacity
+ capacity == Channel.OPTIONAL_CHANNEL -> this.capacity
+ this.capacity == Channel.BUFFERED -> capacity
+ capacity == Channel.BUFFERED -> this.capacity
+ this.capacity == Channel.CONFLATED -> Channel.CONFLATED
+ capacity == Channel.CONFLATED -> Channel.CONFLATED
+ else -> {
+ // sanity checks
+ assert { this.capacity >= 0 }
+ assert { capacity >= 0 }
+ // combine capacities clamping to UNLIMITED on overflow
+ val sum = this.capacity + capacity
+ if (sum >= 0) sum else Channel.UNLIMITED // unlimited on int overflow
+ }
+ }
+ if (newContext == this.context && newCapacity == this.capacity) return this
+ return create(newContext, newCapacity)
+ }
+
+ protected abstract fun create(context: CoroutineContext, capacity: Int): ChannelFlow<T>
+
+ protected abstract suspend fun collectTo(scope: ProducerScope<T>)
+
+ // shared code to create a suspend lambda from collectTo function in one place
+ internal val collectToFun: suspend (ProducerScope<T>) -> Unit
+ get() = { collectTo(it) }
+
+ private val produceCapacity: Int
+ get() = if (capacity == Channel.OPTIONAL_CHANNEL) Channel.BUFFERED else capacity
+
+ open fun broadcastImpl(scope: CoroutineScope, start: CoroutineStart): BroadcastChannel<T> =
+ scope.broadcast(context, produceCapacity, start, block = collectToFun)
+
+ open fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> =
+ scope.produce(context, produceCapacity, block = collectToFun)
+
+ override suspend fun collect(collector: FlowCollector<T>) =
+ coroutineScope {
+ collector.emitAll(produceImpl(this))
+ }
+
+ // debug toString
+ override fun toString(): String =
+ "$classSimpleName[${additionalToStringProps()}context=$context, capacity=$capacity]"
+
+ open fun additionalToStringProps() = ""
+}
+
+// ChannelFlow implementation that operates on another flow before it
+internal abstract class ChannelFlowOperator<S, T>(
+ @JvmField val flow: Flow<S>,
+ context: CoroutineContext,
+ capacity: Int
+) : ChannelFlow<T>(context, capacity) {
+ protected abstract suspend fun flowCollect(collector: FlowCollector<T>)
+
+ // Changes collecting context upstream to the specified newContext, while collecting in the original context
+ private suspend fun collectWithContextUndispatched(collector: FlowCollector<T>, newContext: CoroutineContext) {
+ val originalContextCollector = collector.withUndispatchedContextCollector(coroutineContext)
+ // invoke flowCollect(originalContextCollector) in the newContext
+ return withContextUndispatched(newContext, block = { flowCollect(it) }, value = originalContextCollector)
+ }
+
+ // Slow path when output channel is required
+ protected override suspend fun collectTo(scope: ProducerScope<T>) =
+ flowCollect(SendingCollector(scope))
+
+ // Optimizations for fast-path when channel creation is optional
+ override suspend fun collect(collector: FlowCollector<T>) {
+ // Fast-path: When channel creation is optional (flowOn/flowWith operators without buffer)
+ if (capacity == Channel.OPTIONAL_CHANNEL) {
+ val collectContext = coroutineContext
+ val newContext = collectContext + context // compute resulting collect context
+ // #1: If the resulting context happens to be the same as it was -- fallback to plain collect
+ if (newContext == collectContext)
+ return flowCollect(collector)
+ // #2: If we don't need to change the dispatcher we can go without channels
+ if (newContext[ContinuationInterceptor] == collectContext[ContinuationInterceptor])
+ return collectWithContextUndispatched(collector, newContext)
+ }
+ // Slow-path: create the actual channel
+ super.collect(collector)
+ }
+
+ // debug toString
+ override fun toString(): String = "$flow -> ${super.toString()}"
+}
+
+// Simple channel flow operator: flowOn, buffer, or their fused combination
+internal class ChannelFlowOperatorImpl<T>(
+ flow: Flow<T>,
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = Channel.OPTIONAL_CHANNEL
+) : ChannelFlowOperator<T, T>(flow, context, capacity) {
+ override fun create(context: CoroutineContext, capacity: Int): ChannelFlow<T> =
+ ChannelFlowOperatorImpl(flow, context, capacity)
+
+ override suspend fun flowCollect(collector: FlowCollector<T>) =
+ flow.collect(collector)
+}
+
+// Now if the underlying collector was accepting concurrent emits, then this one is too
+// todo: we might need to generalize this pattern for "thread-safe" operators that can fuse with channels
+private fun <T> FlowCollector<T>.withUndispatchedContextCollector(emitContext: CoroutineContext): FlowCollector<T> = when (this) {
+ // SendingCollector & NopCollector do not care about the context at all and can be used as is
+ is SendingCollector, is NopCollector -> this
+ // Otherwise just wrap into UndispatchedContextCollector interface implementation
+ else -> UndispatchedContextCollector(this, emitContext)
+}
+
+private class UndispatchedContextCollector<T>(
+ downstream: FlowCollector<T>,
+ private val emitContext: CoroutineContext
+) : FlowCollector<T> {
+ private val countOrElement = threadContextElements(emitContext) // precompute for fast withContextUndispatched
+ private val emitRef: suspend (T) -> Unit = { downstream.emit(it) } // allocate suspend function ref once on creation
+
+ override suspend fun emit(value: T): Unit =
+ withContextUndispatched(emitContext, countOrElement, emitRef, value)
+}
+
+// Efficiently computes block(value) in the newContext
+private suspend fun <T, V> withContextUndispatched(
+ newContext: CoroutineContext,
+ countOrElement: Any = threadContextElements(newContext), // can be precomputed for speed
+ block: suspend (V) -> T, value: V
+): T =
+ suspendCoroutineUninterceptedOrReturn sc@{ uCont ->
+ withCoroutineContext(newContext, countOrElement) {
+ block.startCoroutineUninterceptedOrReturn(value, Continuation(newContext) {
+ uCont.resumeWith(it)
+ })
+ }
+ }
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt b/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt
new file mode 100644
index 00000000..f7edad08
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016-2019 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
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.selects.*
+
+internal fun getNull(): Symbol = NULL // Workaround for JS BE bug
+
+internal suspend fun <T1, T2, R> FlowCollector<R>.combineTransformInternal(
+ first: Flow<T1>, second: Flow<T2>,
+ transform: suspend FlowCollector<R>.(a: T1, b: T2) -> Unit
+) {
+ coroutineScope {
+ val firstChannel = asFairChannel(first)
+ val secondChannel = asFairChannel(second)
+ var firstValue: Any? = null
+ var secondValue: Any? = null
+ var firstIsClosed = false
+ var secondIsClosed = false
+ while (!firstIsClosed || !secondIsClosed) {
+ select<Unit> {
+ onReceive(firstIsClosed, firstChannel, { firstIsClosed = true }) { value ->
+ firstValue = value
+ if (secondValue !== null) {
+ transform(getNull().unbox(firstValue), getNull().unbox(secondValue) as T2)
+ }
+ }
+
+ onReceive(secondIsClosed, secondChannel, { secondIsClosed = true }) { value ->
+ secondValue = value
+ if (firstValue !== null) {
+ transform(getNull().unbox(firstValue) as T1, getNull().unbox(secondValue) as T2)
+ }
+ }
+ }
+ }
+ }
+}
+
+@PublishedApi
+internal suspend fun <R, T> FlowCollector<R>.combineInternal(
+ flows: Array<out Flow<T>>,
+ arrayFactory: () -> Array<T?>,
+ transform: suspend FlowCollector<R>.(Array<T>) -> Unit
+) {
+ coroutineScope {
+ val size = flows.size
+ val channels =
+ Array(size) { asFairChannel(flows[it]) }
+ val latestValues = arrayOfNulls<Any?>(size)
+ val isClosed = Array(size) { false }
+
+ // See flow.combine(other) for explanation.
+ while (!isClosed.all { it }) {
+ select<Unit> {
+ for (i in 0 until size) {
+ onReceive(isClosed[i], channels[i], { isClosed[i] = true }) { value ->
+ latestValues[i] = value
+ if (latestValues.all { it !== null }) {
+ val arguments = arrayFactory()
+ for (index in 0 until size) {
+ arguments[index] = NULL.unbox(latestValues[index])
+ }
+ transform(arguments as Array<T>)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private inline fun SelectBuilder<Unit>.onReceive(
+ isClosed: Boolean,
+ channel: ReceiveChannel<Any>,
+ crossinline onClosed: () -> Unit,
+ noinline onReceive: suspend (value: Any) -> Unit
+) {
+ if (isClosed) return
+ channel.onReceiveOrNull {
+ // TODO onReceiveOrClosed when boxing issues are fixed
+ if (it === null) onClosed()
+ else onReceive(it)
+ }
+}
+
+// Channel has any type due to onReceiveOrNull. This will be fixed after receiveOrClosed
+private fun CoroutineScope.asFairChannel(flow: Flow<*>): ReceiveChannel<Any> = produce {
+ val channel = channel as ChannelCoroutine<Any>
+ flow.collect { value ->
+ return@collect channel.sendFair(value ?: NULL)
+ }
+}
+
+internal fun <T1, T2, R> zipImpl(flow: Flow<T1>, flow2: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = unsafeFlow {
+ coroutineScope {
+ val first = asChannel(flow)
+ val second = asChannel(flow2)
+ /*
+ * This approach only works with rendezvous channel and is required to enforce correctness
+ * in the following scenario:
+ * ```
+ * val f1 = flow { emit(1); delay(Long.MAX_VALUE) }
+ * val f2 = flowOf(1)
+ * f1.zip(f2) { ... }
+ * ```
+ *
+ * Invariant: this clause is invoked only when all elements from the channel were processed (=> rendezvous restriction).
+ */
+ (second as SendChannel<*>).invokeOnClose {
+ if (!first.isClosedForReceive) first.cancel(AbortFlowException())
+ }
+
+ val otherIterator = second.iterator()
+ try {
+ first.consumeEach { value ->
+ if (!otherIterator.hasNext()) {
+ return@consumeEach
+ }
+ emit(transform(NULL.unbox(value), NULL.unbox(otherIterator.next())))
+ }
+ } catch (e: AbortFlowException) {
+ // complete
+ } finally {
+ if (!second.isClosedForReceive) second.cancel(AbortFlowException())
+ }
+ }
+}
+
+// Channel has any type due to onReceiveOrNull. This will be fixed after receiveOrClosed
+private fun CoroutineScope.asChannel(flow: Flow<*>): ReceiveChannel<Any> = produce {
+ flow.collect { value ->
+ return@collect channel.send(value ?: NULL)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
new file mode 100644
index 00000000..1917afb8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Creates a [CoroutineScope] and calls the specified suspend block with this scope.
+ * This builder is similar to [coroutineScope] with the only exception that it *ties* lifecycle of children
+ * and itself regarding the cancellation, thus being cancelled when one of the children becomes cancelled.
+ *
+ * For example:
+ * ```
+ * flowScope {
+ * launch {
+ * throw CancellationException()
+ * }
+ * } // <- CE will be rethrown here
+ * ```
+ */
+internal suspend fun <R> flowScope(@BuilderInference block: suspend CoroutineScope.() -> R): R =
+ suspendCoroutineUninterceptedOrReturn { uCont ->
+ val coroutine = FlowCoroutine(uCont.context, uCont)
+ coroutine.startUndispatchedOrReturn(coroutine, block)
+ }
+
+/**
+ * Creates a flow that also provides a [CoroutineScope] for each collector
+ * Shorthand for:
+ * ```
+ * flow {
+ * flowScope {
+ * ...
+ * }
+ * }
+ * ```
+ * with additional constraint on cancellation.
+ * To cancel child without cancelling itself, `cancel(ChildCancelledException())` should be used.
+ */
+internal fun <R> scopedFlow(@BuilderInference block: suspend CoroutineScope.(FlowCollector<R>) -> Unit): Flow<R> =
+ flow {
+ val collector = this
+ flowScope { block(collector) }
+ }
+
+internal fun <T> CoroutineScope.flowProduce(
+ context: CoroutineContext,
+ capacity: Int = 0,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): ReceiveChannel<T> {
+ val channel = Channel<T>(capacity)
+ val newContext = newCoroutineContext(context)
+ val coroutine = FlowProduceCoroutine(newContext, channel)
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+ return coroutine
+}
+
+private class FlowCoroutine<T>(
+ context: CoroutineContext,
+ uCont: Continuation<T>
+) : ScopeCoroutine<T>(context, uCont) {
+ public override fun childCancelled(cause: Throwable): Boolean {
+ if (cause is ChildCancelledException) return true
+ return cancelImpl(cause)
+ }
+}
+
+private class FlowProduceCoroutine<T>(
+ parentContext: CoroutineContext,
+ channel: Channel<T>
+) : ProducerCoroutine<T>(parentContext, channel) {
+ public override fun childCancelled(cause: Throwable): Boolean {
+ if (cause is ChildCancelledException) return true
+ return cancelImpl(cause)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt b/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt
new file mode 100644
index 00000000..c3a85a3d
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+
+/**
+ * This exception is thrown when operator need no more elements from the flow.
+ * This exception should never escape outside of operator's implementation.
+ */
+internal expect class AbortFlowException() : CancellationException
+
+/**
+ * Exception used to cancel child of [scopedFlow] without cancelling the whole scope.
+ */
+internal expect class ChildCancelledException() : CancellationException
+
+@Suppress("NOTHING_TO_INLINE")
+@PublishedApi
+internal inline fun checkIndexOverflow(index: Int): Int {
+ if (index < 0) {
+ throw ArithmeticException("Index overflow has happened")
+ }
+ return index
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt b/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
new file mode 100644
index 00000000..289a4ebc
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.sync.*
+import kotlin.coroutines.*
+
+internal class ChannelFlowTransformLatest<T, R>(
+ private val transform: suspend FlowCollector<R>.(value: T) -> Unit,
+ flow: Flow<T>,
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = Channel.BUFFERED
+) : ChannelFlowOperator<T, R>(flow, context, capacity) {
+ override fun create(context: CoroutineContext, capacity: Int): ChannelFlow<R> =
+ ChannelFlowTransformLatest(transform, flow, context, capacity)
+
+ override suspend fun flowCollect(collector: FlowCollector<R>) {
+ assert { collector is SendingCollector } // So cancellation behaviour is not leaking into the downstream
+ flowScope {
+ var previousFlow: Job? = null
+ flow.collect { value ->
+ previousFlow?.apply {
+ cancel(ChildCancelledException())
+ join()
+ }
+ // Do not pay for dispatch here, it's never necessary
+ previousFlow = launch(start = CoroutineStart.UNDISPATCHED) {
+ collector.transform(value)
+ }
+ }
+ }
+ }
+}
+
+internal class ChannelFlowMerge<T>(
+ private val flow: Flow<Flow<T>>,
+ private val concurrency: Int,
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = Channel.BUFFERED
+) : ChannelFlow<T>(context, capacity) {
+ override fun create(context: CoroutineContext, capacity: Int): ChannelFlow<T> =
+ ChannelFlowMerge(flow, concurrency, context, capacity)
+
+ override fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> {
+ return scope.flowProduce(context, capacity, block = collectToFun)
+ }
+
+ override suspend fun collectTo(scope: ProducerScope<T>) {
+ val semaphore = Semaphore(concurrency)
+ val collector = SendingCollector(scope)
+ val job: Job? = coroutineContext[Job]
+ flow.collect { inner ->
+ /*
+ * We launch a coroutine on each emitted element and the only potential
+ * suspension point in this collector is `semaphore.acquire` that rarely suspends,
+ * so we manually check for cancellation to propagate it to the upstream in time.
+ */
+ job?.ensureActive()
+ semaphore.acquire()
+ scope.launch {
+ try {
+ inner.collect(collector)
+ } finally {
+ semaphore.release() // Release concurrency permit
+ }
+ }
+ }
+ }
+
+ override fun additionalToStringProps(): String =
+ "concurrency=$concurrency, "
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt b/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt
new file mode 100644
index 00000000..d1b6ad2b
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.flow.*
+
+internal object NopCollector : FlowCollector<Any?> {
+ override suspend fun emit(value: Any?) {
+ // does nothing
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt b/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt
new file mode 100644
index 00000000..c6ff12fc
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.internal.*
+import kotlin.jvm.*
+
+/**
+ * This value is used a a surrogate `null` value when needed.
+ * It should never leak to the outside world.
+ */
+@JvmField
+@SharedImmutable
+internal val NULL = Symbol("NULL")
+
+/*
+ * Symbol used to indicate that the flow is complete.
+ * It should never leak to the outside world.
+ */
+@JvmField
+@SharedImmutable
+internal val DONE = Symbol("DONE")
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.kt
new file mode 100644
index 00000000..fec0ee96
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+
+internal class SafeCollector<T>(
+ private val collector: FlowCollector<T>,
+ private val collectContext: CoroutineContext
+) : FlowCollector<T> {
+
+ // Note, it is non-capturing lambda, so no extra allocation during init of SafeCollector
+ private val collectContextSize = collectContext.fold(0) { count, _ -> count + 1 }
+ private var lastEmissionContext: CoroutineContext? = null
+
+ override suspend fun emit(value: T) {
+ /*
+ * Benign data-race here:
+ * We read potentially racy published coroutineContext, but we only use it for
+ * referential comparison (=> thus safe) and are not using it for structural comparisons.
+ */
+ val currentContext = coroutineContext
+ // This check is triggered once per flow on happy path.
+ if (lastEmissionContext !== currentContext) {
+ checkContext(currentContext)
+ lastEmissionContext = currentContext
+ }
+ collector.emit(value) // TCE
+ }
+
+ private fun checkContext(currentContext: CoroutineContext) {
+ val result = currentContext.fold(0) fold@{ count, element ->
+ val key = element.key
+ val collectElement = collectContext[key]
+ if (key !== Job) {
+ return@fold if (element !== collectElement) Int.MIN_VALUE
+ else count + 1
+ }
+
+ val collectJob = collectElement as Job?
+ val emissionParentJob = (element as Job).transitiveCoroutineParent(collectJob)
+ /*
+ * Things like
+ * ```
+ * coroutineScope {
+ * launch {
+ * emit(1)
+ * }
+ *
+ * launch {
+ * emit(2)
+ * }
+ * }
+ * ```
+ * are prohibited because 'emit' is not thread-safe by default. Use channelFlow instead if you need concurrent emission
+ * or want to switch context dynamically (e.g. with `withContext`).
+ *
+ * Note that collecting from another coroutine is allowed, e.g.:
+ * ```
+ * coroutineScope {
+ * val channel = produce {
+ * collect { value ->
+ * send(value)
+ * }
+ * }
+ * channel.consumeEach { value ->
+ * emit(value)
+ * }
+ * }
+ * ```
+ * is a completely valid.
+ */
+ if (emissionParentJob !== collectJob) {
+ error(
+ "Flow invariant is violated:\n" +
+ "\t\tEmission from another coroutine is detected.\n" +
+ "\t\tChild of $emissionParentJob, expected child of $collectJob.\n" +
+ "\t\tFlowCollector is not thread-safe and concurrent emissions are prohibited.\n" +
+ "\t\tTo mitigate this restriction please use 'channelFlow' builder instead of 'flow'"
+ )
+ }
+
+ /*
+ * If collect job is null (-> EmptyCoroutineContext, probably run from `suspend fun main`), then invariant is maintained
+ * (common transitive parent is "null"), but count check will fail, so just do not count job context element when
+ * flow is collected from EmptyCoroutineContext
+ */
+ if (collectJob == null) count else count + 1
+ }
+ if (result != collectContextSize) {
+ error(
+ "Flow invariant is violated:\n" +
+ "\t\tFlow was collected in $collectContext,\n" +
+ "\t\tbut emission happened in $currentContext.\n" +
+ "\t\tPlease refer to 'flow' documentation or use 'flowOn' instead"
+ )
+ }
+ }
+
+ private tailrec fun Job?.transitiveCoroutineParent(collectJob: Job?): Job? {
+ if (this === null) return null
+ if (this === collectJob) return this
+ if (this !is ScopeCoroutine<*>) return this
+ return parent.transitiveCoroutineParent(collectJob)
+ }
+}
+
+/**
+ * An analogue of the [flow] builder that does not check the context of execution of the resulting flow.
+ * Used in our own operators where we trust the context of invocations.
+ */
+@PublishedApi
+internal inline fun <T> unsafeFlow(@BuilderInference crossinline block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
+ return object : Flow<T> {
+ override suspend fun collect(collector: FlowCollector<T>) {
+ collector.block()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt b/kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt
new file mode 100644
index 00000000..b6d578fe
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+
+/**
+ * Collection that sends to channel
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public class SendingCollector<T>(
+ private val channel: SendChannel<T>
+) : FlowCollector<T> {
+ override suspend fun emit(value: T) = channel.send(value)
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
new file mode 100644
index 00000000..043c839f
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
+import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
+import kotlinx.coroutines.flow.internal.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Buffers flow emissions via channel of a specified capacity and runs collector in a separate coroutine.
+ *
+ * Normally, [flows][Flow] are _sequential_. It means that the code of all operators is executed in the
+ * same coroutine. For example, consider the following code using [onEach] and [collect] operators:
+ *
+ * ```
+ * flowOf("A", "B", "C")
+ * .onEach { println("1$it") }
+ * .collect { println("2$it") }
+ * ```
+ *
+ * It is going to be executed in the following order by the coroutine `Q` that calls this code:
+ *
+ * ```
+ * Q : -->-- [1A] -- [2A] -- [1B] -- [2B] -- [1C] -- [2C] -->--
+ * ```
+ *
+ * So if the operator's code takes considerable time to execute, then the total execution time is going to be
+ * the sum of execution times for all operators.
+ *
+ * The `buffer` operator creates a separate coroutine during execution for the flow it applies to.
+ * Consider the following code:
+ *
+ * ```
+ * flowOf("A", "B", "C")
+ * .onEach { println("1$it") }
+ * .buffer() // <--------------- buffer between onEach and collect
+ * .collect { println("2$it") }
+ * ```
+ *
+ * It will use two coroutines for execution of the code. A coroutine `Q` that calls this code is
+ * going to execute `collect`, and the code before `buffer` will be executed in a separate
+ * new coroutine `P` concurrently with `Q`:
+ *
+ * ```
+ * P : -->-- [1A] -- [1B] -- [1C] ---------->-- // flowOf(...).onEach { ... }
+ *
+ * |
+ * | channel // buffer()
+ * V
+ *
+ * Q : -->---------- [2A] -- [2B] -- [2C] -->-- // collect
+ * ```
+ *
+ * When operator's code takes time to execute this decreases the total execution time of the flow.
+ * A [channel][Channel] is used between the coroutines to send elements emitted by the coroutine `P` to
+ * the coroutine `Q`. If the code before `buffer` operator (in the coroutine `P`) is faster than the code after
+ * `buffer` operator (in the coroutine `Q`), then this channel will become full at some point and will suspend
+ * the producer coroutine `P` until the consumer coroutine `Q` catches up.
+ * The [capacity] parameter defines the size of this buffer.
+ *
+ * ### Operator fusion
+ *
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] 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,
+ * which effectively requests a buffer of any size. Multiple requests with a specified buffer
+ * size produce a buffer with the sum of the requested buffer sizes.
+ *
+ * ### Conceptual implementation
+ *
+ * The actual implementation of `buffer` is not trivial due to the fusing, but conceptually its
+ * implementation is equivalent to the following code that can be written using [produce]
+ * coroutine builder to produce a channel and [consumeEach][ReceiveChannel.consumeEach] extension to consume it:
+ *
+ * ```
+ * fun <T> Flow<T>.buffer(capacity: Int = DEFAULT): Flow<T> = flow {
+ * coroutineScope { // limit the scope of concurrent producer coroutine
+ * val channel = produce(capacity = capacity) {
+ * collect { send(it) } // send all to channel
+ * }
+ * // emit all received values
+ * channel.consumeEach { emit(it) }
+ * }
+ * }
+ * ```
+ *
+ * ### Conflation
+ *
+ * Usage of this function with [capacity] of [Channel.CONFLATED][Channel.CONFLATED] is provided as a shortcut via
+ * [conflate] operator. See its documentation for details.
+ *
+ * @param capacity type/capacity of the buffer between coroutines. Allowed values are the same as in `Channel(...)`
+ * factory function: [BUFFERED][Channel.BUFFERED] (by default), [CONFLATED][Channel.CONFLATED],
+ * [RENDEZVOUS][Channel.RENDEZVOUS], [UNLIMITED][Channel.UNLIMITED] or a non-negative value indicating
+ * an explicitly requested size.
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.buffer(capacity: Int = BUFFERED): Flow<T> {
+ require(capacity >= 0 || capacity == BUFFERED || capacity == CONFLATED) {
+ "Buffer size should be non-negative, BUFFERED, or CONFLATED, but was $capacity"
+ }
+ return if (this is ChannelFlow)
+ update(capacity = capacity)
+ else
+ ChannelFlowOperatorImpl(this, capacity = capacity)
+}
+
+/**
+ * Conflates flow emissions via conflated channel and runs collector in a separate coroutine.
+ * The effect of this is that emitter is never suspended due to a slow collector, but collector
+ * always gets the most recent value emitted.
+ *
+ * For example, consider the flow that emits integers from 1 to 30 with 100 ms delay between them:
+ *
+ * ```
+ * val flow = flow {
+ * for (i in 1..30) {
+ * delay(100)
+ * emit(i)
+ * }
+ * }
+ * ```
+ *
+ * Applying `conflate()` operator to it allows a collector that delays 1 second on each element to get
+ * integers 1, 10, 20, 30:
+ *
+ * ```
+ * val result = flow.conflate().onEach { delay(1000) }.toList()
+ * assertEquals(listOf(1, 10, 20, 30), result)
+ * ```
+ *
+ * Note that `conflate` operator is a shortcut for [buffer] with `capacity` of [Channel.CONFLATED][Channel.CONFLATED].
+ *
+ * ### Operator fusion
+ *
+ * Adjacent applications of `conflate`/[buffer], [channelFlow], [flowOn], [produceIn], and [broadcastIn] are
+ * always fused so that only one properly configured channel is used for execution.
+ * **Conflation takes precedence over `buffer()` calls with any other capacity.**
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.conflate(): Flow<T> = buffer(CONFLATED)
+
+/**
+ * Changes the context where this flow is executed to the given [context].
+ * This operator is composable and affects only preceding operators that do not have its own context.
+ * This operator is context preserving: [context] **does not** leak into the downstream flow.
+ *
+ * For example:
+ *
+ * ```
+ * withContext(Dispatchers.Main) {
+ * val singleValue = intFlow // will be executed on IO if context wasn't specified before
+ * .map { ... } // Will be executed in IO
+ * .flowOn(Dispatchers.IO)
+ * .filter { ... } // Will be executed in Default
+ * .flowOn(Dispatchers.Default)
+ * .single() // Will be executed in the Main
+ * }
+ * ```
+ *
+ * For more explanation of context preservation please refer to [Flow] documentation.
+ *
+ * This operators retains a _sequential_ nature of flow if changing the context does not call for changing
+ * the [dispatcher][CoroutineDispatcher]. Otherwise, if changing dispatcher is required, it collects
+ * flow emissions in one coroutine that is run using a specified [context] and emits them from another coroutines
+ * with the original collector's context using a channel with a [default][Channel.BUFFERED] buffer size
+ * between two coroutines similarly to [buffer] operator, unless [buffer] operator is explicitly called
+ * before or after `flowOn`, which requests buffering behavior and specifies channel size.
+ *
+ * ### Operator fusion
+ *
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] 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
+ * the first `flowOn` operator naturally take precedence over the elements of the second `flowOn` operator
+ * when they have the same context keys, for example:
+ *
+ * ```
+ * flow.map { ... } // Will be executed in IO
+ * .flowOn(Dispatchers.IO) // This one takes precedence
+ * .flowOn(Dispatchers.Default)
+ * ```
+ *
+ * @throws [IllegalArgumentException] if provided context contains [Job] instance.
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.flowOn(context: CoroutineContext): Flow<T> {
+ checkFlowContext(context)
+ return when {
+ context == EmptyCoroutineContext -> this
+ this is ChannelFlow -> update(context = context)
+ else -> ChannelFlowOperatorImpl(this, context = context)
+ }
+}
+
+/**
+ * The operator that changes the context where all transformations applied to the given flow within a [builder] are executed.
+ * This operator is context preserving and does not affect the context of the preceding and subsequent operations.
+ *
+ * Example:
+ *
+ * ```
+ * flow // not affected
+ * .map { ... } // Not affected
+ * .flowWith(Dispatchers.IO) {
+ * map { ... } // in IO
+ * .filter { ... } // in IO
+ * }
+ * .map { ... } // Not affected
+ * ```
+ *
+ * For more explanation of context preservation please refer to [Flow] documentation.
+ *
+ * This operator is deprecated without replacement because it was discovered that it doesn't play well with coroutines
+ * and flow semantics:
+ *
+ * 1) It doesn't prevent context elements from the downstream to leak into its body
+ * ```
+ * flowOf(1).flowWith(EmptyCoroutineContext) {
+ * onEach { println(kotlin.coroutines.coroutineContext[CoroutineName]) } // Will print 42
+ * }.flowOn(CoroutineName(42))
+ * ```
+ * 2) To avoid such leaks, new primitive should be introduced to `kotlinx.coroutines` -- the subtraction of contexts.
+ * And this will become a new concept to learn, maintain and explain.
+ * 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.
+ */
+@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
+public fun <T, R> Flow<T>.flowWith(
+ flowContext: CoroutineContext,
+ bufferSize: Int = BUFFERED,
+ builder: Flow<T>.() -> Flow<R>
+): Flow<R> {
+ checkFlowContext(flowContext)
+ val source = this
+ return unsafeFlow {
+ /**
+ * Here we should remove a Job instance from the context.
+ * All builders are written using scoping and no global coroutines are launched, so it is safe not to provide explicit Job.
+ * It is also necessary not to mess with cancellation if multiple flowWith are used.
+ */
+ val originalContext = coroutineContext.minusKey(Job)
+ val prepared = source.flowOn(originalContext).buffer(bufferSize)
+ builder(prepared).flowOn(flowContext).buffer(bufferSize).collect { value ->
+ return@collect emit(value)
+ }
+ }
+}
+
+private fun checkFlowContext(context: CoroutineContext) {
+ require(context[Job] == null) {
+ "Flow context cannot contain job in it. Had $context"
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
new file mode 100644
index 00000000..5f2f4662
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.selects.*
+import kotlin.jvm.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Returns a flow that mirrors the original flow, but filters out values
+ * that are followed by the newer values within the given [timeout][timeoutMillis].
+ * The latest value is always emitted.
+ *
+ * Example:
+ * ```
+ * flow {
+ * emit(1)
+ * delay(90)
+ * emit(2)
+ * delay(90)
+ * emit(3)
+ * delay(1010)
+ * emit(4)
+ * delay(1010)
+ * emit(5)
+ * }.debounce(1000)
+ * ```
+ * produces `3, 4, 5`.
+ *
+ * Note that the resulting flow does not emit anything as long as the original flow emits
+ * items faster than every [timeoutMillis] milliseconds.
+ */
+@FlowPreview
+public fun <T> Flow<T>.debounce(timeoutMillis: Long): Flow<T> {
+ require(timeoutMillis > 0) { "Debounce timeout should be positive" }
+ return scopedFlow { downstream ->
+ // Actually Any, KT-30796
+ val values = produce<Any?>(capacity = Channel.CONFLATED) {
+ collect { value -> send(value ?: NULL) }
+ }
+ var lastValue: Any? = null
+ while (lastValue !== DONE) {
+ select<Unit> {
+ // Should be receiveOrClosed when boxing issues are fixed
+ values.onReceiveOrNull {
+ if (it == null) {
+ if (lastValue != null) downstream.emit(NULL.unbox(lastValue))
+ lastValue = DONE
+ } else {
+ lastValue = it
+ }
+ }
+
+ lastValue?.let { value ->
+ // set timeout when lastValue != null
+ onTimeout(timeoutMillis) {
+ lastValue = null // Consume the value
+ downstream.emit(NULL.unbox(value))
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Returns a flow that emits only the latest value emitted by the original flow during the given sampling [period][periodMillis].
+ *
+ * Example:
+ * ```
+ * flow {
+ * repeat(10) {
+ * emit(it)
+ * delay(50)
+ * }
+ * }.sample(100)
+ * ```
+ * produces `1, 3, 5, 7, 9`.
+ *
+ * Note that the latest element is not emitted if it does not fit into the sampling window.
+ */
+@FlowPreview
+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
+ 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
+ }
+ }
+
+ // todo: shall be start sampling only when an element arrives or sample aways as here?
+ ticker.onReceive {
+ val value = lastValue ?: return@onReceive
+ lastValue = null // Consume the value
+ downstream.emit(NULL.unbox(value))
+ }
+ }
+ }
+ }
+}
+
+/*
+ * TODO this design (and design of the corresponding operator) depends on #540
+ */
+internal fun CoroutineScope.fixedPeriodTicker(delayMillis: Long, initialDelayMillis: Long = delayMillis): ReceiveChannel<Unit> {
+ require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" }
+ require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" }
+ return produce(capacity = 0) {
+ delay(initialDelayMillis)
+ while (true) {
+ channel.send(Unit)
+ delay(delayMillis)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
new file mode 100644
index 00000000..89491f41
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.jvm.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Returns flow where all subsequent repetitions of the same value are filtered out.
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.distinctUntilChanged(): Flow<T> = distinctUntilChangedBy { it }
+
+/**
+ * Returns flow where all subsequent repetitions of the same value are filtered out, when compared
+ * with each other via the provided [areEquivalent] function.
+ */
+@FlowPreview
+public fun <T> Flow<T>.distinctUntilChanged(areEquivalent: (old: T, new: T) -> Boolean): Flow<T> =
+ distinctUntilChangedBy(keySelector = { it }, areEquivalent = areEquivalent)
+
+/**
+ * Returns flow where all subsequent repetitions of the same key are filtered out, where
+ * key is extracted with [keySelector] function.
+ */
+@FlowPreview
+public fun <T, K> Flow<T>.distinctUntilChangedBy(keySelector: (T) -> K): Flow<T> =
+ distinctUntilChangedBy(keySelector = keySelector, areEquivalent = { old, new -> old == new })
+
+/**
+ * Returns flow where all subsequent repetitions of the same key are filtered out, where
+ * keys are extracted with [keySelector] function and compared with each other via the
+ * provided [areEquivalent] function.
+ */
+private inline fun <T, K> Flow<T>.distinctUntilChangedBy(
+ crossinline keySelector: (T) -> K,
+ crossinline areEquivalent: (old: K, new: K) -> Boolean
+): Flow<T> =
+ flow {
+ var previousKey: Any? = NULL
+ collect { value ->
+ val key = keySelector(value)
+ @Suppress("UNCHECKED_CAST")
+ if (previousKey === NULL || !areEquivalent(previousKey as K, key)) {
+ previousKey = key
+ emit(value)
+ }
+ }
+ }
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt b/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt
new file mode 100644
index 00000000..f3a11268
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+@file:Suppress("UNCHECKED_CAST")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+// ------------------ WARNING ------------------
+// These emitting operators must use safe flow builder, because their allow
+// user code to directly emit to the underlying FlowCollector.
+
+/**
+ * Applies [transform] function to each value of the given flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ *
+ * This operator can be used as a building block for other operators, for example:
+ *
+ * ```
+ * fun Flow<Int>.skipOddAndDuplicateEven(): Flow<Int> = transform { value ->
+ * if (value % 2 == 0) { // Emit only even values, but twice
+ * emit(value)
+ * emit(value)
+ * } // Do nothing if odd
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T, R> Flow<T>.transform(
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
+): Flow<R> = flow { // Note: safe flow is used here, because collector is exposed to transform on each operation
+ collect { value ->
+ // kludge, without it Unit will be returned and TCE won't kick in, KT-28938
+ return@collect transform(value)
+ }
+}
+
+// For internal operator implementation
+@PublishedApi
+internal inline fun <T, R> Flow<T>.unsafeTransform(
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
+): Flow<R> = unsafeFlow { // Note: unsafe flow is used here, because unsafeTransform is only for internal use
+ collect { value ->
+ // kludge, without it Unit will be returned and TCE won't kick in, KT-28938
+ return@collect transform(value)
+ }
+}
+
+/**
+ * Invokes the given [action] when the this flow starts to be collected.
+ *
+ * The receiver of the [action] is [FlowCollector] and thus `onStart` can emit additional elements.
+ * For example:
+ *
+ * ```
+ * flowOf("a", "b", "c")
+ * .onStart { emit("Begin") }
+ * .collect { println(it) } // prints Begin, a, b, c
+ * ```
+ */
+@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
+public fun <T> Flow<T>.onStart(
+ action: suspend FlowCollector<T>.() -> Unit
+): Flow<T> = unsafeFlow { // Note: unsafe flow is used here, but safe collector is used to invoke start action
+ SafeCollector<T>(this, coroutineContext).action()
+ collect(this) // directly delegate
+}
+
+/**
+ * Invokes the given [action] when the given flow is completed or cancelled, using
+ * the exception from the upstream (if any) as cause parameter of [action].
+ *
+ * Conceptually, `onCompletion` is similar to wrapping the flow collection into a `finally` block,
+ * for example the following imperative snippet:
+ *
+ * ```
+ * try {
+ * myFlow.collect { value ->
+ * println(value)
+ * }
+ * } finally {
+ * println("Done")
+ * }
+ * ```
+ *
+ * can be replaced with a declarative one using `onCompletion`:
+ *
+ * ```
+ * myFlow
+ * .onEach { println(it) }
+ * .onCompletion { println("Done") }
+ * .collect()
+ * ```
+ *
+ * This operator is *transparent* to exceptions that occur in downstream flow
+ * and does not observe exceptions that are thrown to cancel the flow,
+ * while any exception from the [action] will be thrown downstream.
+ * This behaviour can be demonstrated by the following example:
+ *
+ * ```
+ * flow { emitData() }
+ * .map { computeOne(it) }
+ * .onCompletion { println(it) } // Can print exceptions from emitData and computeOne
+ * .map { computeTwo(it) }
+ * .onCompletion { println(it) } // Can print exceptions from emitData, computeOne, onCompletion and computeTwo
+ * .collect()
+ * ```
+ *
+ * The receiver of the [action] is [FlowCollector] and this operator can be used to emit additional
+ * elements at the end of the collection. For example:
+ *
+ * ```
+ * flowOf("a", "b", "c")
+ * .onCompletion { emit("Done") }
+ * .collect { println(it) } // prints a, b, c, Done
+ * ```
+ */
+@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
+public fun <T> Flow<T>.onCompletion(
+ action: suspend FlowCollector<T>.(cause: Throwable?) -> Unit
+): Flow<T> = unsafeFlow { // Note: unsafe flow is used here, but safe collector is used to invoke completion action
+ var exception: Throwable? = null
+ try {
+ exception = catchImpl(this)
+ } finally {
+ // Separate method because of KT-32220
+ SafeCollector<T>(this, coroutineContext).invokeSafely(action, exception)
+ exception?.let { throw it }
+ }
+}
+
+// It was only released in 1.3.0-M2, remove in 1.4.0
+/** @suppress */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "binary compatibility with a version w/o FlowCollector receiver")
+public fun <T> Flow<T>.onCompletion(action: suspend (cause: Throwable?) -> Unit) =
+ onCompletion { action(it) }
+
+private suspend fun <T> FlowCollector<T>.invokeSafely(
+ action: suspend FlowCollector<T>.(cause: Throwable?) -> Unit,
+ cause: Throwable?
+) {
+ try {
+ action(cause)
+ } catch (e: Throwable) {
+ if (cause !== null) e.addSuppressedThrowable(cause)
+ throw e
+ }
+}
+
+
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
new file mode 100644
index 00000000..9b7a91f1
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Catches exceptions in the flow completion and calls a specified [action] with
+ * the caught exception. This operator is *transparent* to exceptions that occur
+ * in downstream flow and does not catch exceptions that are thrown to cancel the flow.
+ *
+ * For example:
+ *
+ * ```
+ * flow { emitData() }
+ * .map { computeOne(it) }
+ * .catch { ... } // catches exceptions in emitData and computeOne
+ * .map { computeTwo(it) }
+ * .collect { process(it) } // throws exceptions from process and computeTwo
+ * ```
+ *
+ * Conceptually, the action of `catch` operator is similar to wrapping the code of upstream flows with
+ * `try { ... } catch (e: Throwable) { action(e) }`.
+ *
+ * Any exception in the [action] code itself proceeds downstream where it can be
+ * caught by further `catch` operators if needed. If a particular exception does not need to be
+ * caught it can be rethrown from the action of `catch` operator. For example:
+ *
+ * ```
+ * flow.catch { e ->
+ * if (e !is IOException) throw e // rethrow all but IOException
+ * // e is IOException here
+ * ...
+ * }
+ * ```
+ *
+ * The [action] code has [FlowCollector] as a receiver and can [emit][FlowCollector.emit] values downstream.
+ * For example, caught exception can be replaced with some wrapper value for errors:
+ *
+ * ```
+ * flow.catch { e -> emit(ErrorWrapperValue(e)) }
+ * ```
+ *
+ * The [action] can also use [emitAll] to fallback on some other flow in case of an error. However, to
+ * retry an original flow use [retryWhen] operator that can retry the flow multiple times without
+ * introducing ever-growing stack of suspending calls.
+ */
+@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
+public fun <T> Flow<T>.catch(action: suspend FlowCollector<T>.(cause: Throwable) -> Unit): Flow<T> =
+ flow {
+ val exception = catchImpl(this)
+ if (exception != null) action(exception)
+ }
+
+/**
+ * @suppress **Deprecated**: Use `(Throwable) -> Boolean` functional type
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Use (Throwable) -> Boolean functional type",
+ replaceWith = ReplaceWith("(Throwable) -> Boolean")
+)
+public typealias ExceptionPredicate = (Throwable) -> Boolean
+
+/**
+ * Switches to the [fallback] flow if the original flow throws an exception that matches the [predicate].
+ * Cancellation exceptions that were caused by the direct [cancel] call are not handled by this operator.
+ *
+ * @suppress **Deprecated**: Use `catch { e -> if (predicate(e)) emitAll(fallback) else throw e }`
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Use catch { e -> if (predicate(e)) emitAll(fallback) else throw e }",
+ replaceWith = ReplaceWith("catch { e -> if (predicate(e)) emitAll(fallback) else throw e }")
+)
+public fun <T> Flow<T>.onErrorCollect(
+ fallback: Flow<T>,
+ predicate: (Throwable) -> Boolean = { true }
+): Flow<T> = catch { e ->
+ if (!predicate(e)) throw e
+ emitAll(fallback)
+}
+
+/**
+ * Retries collection of the given flow up to [retries] times when an exception that matches the
+ * given [predicate] occurs in the upstream flow. This operator is *transparent* to exceptions that occur
+ * in downstream flow and does not retry on exceptions that are thrown to cancel the flow.
+ *
+ * See [catch] for details on how exceptions are caught in flows.
+ *
+ * The default value of [retries] parameter is [Long.MAX_VALUE]. This value effectively means to retry forever.
+ * This operator is a shorthand for the following code (see [retryWhen]). Note that `attempt` is checked first
+ * and [predicate] is not called when it reaches the given number of [retries]:
+ *
+ * ```
+ * retryWhen { cause, attempt -> attempt < retries && predicate(cause) }
+ * ```
+ *
+ * The [predicate] parameter is always true by default. The [predicate] is a suspending function,
+ * so it can be also used to introduce delay before retry, for example:
+ *
+ * ```
+ * flow.retry(3) { e ->
+ * // retry on any IOException but also introduce delay if retrying
+ * (e is IOException).also { if (it) delay(1000) }
+ * }
+ * ```
+ *
+ * @throws IllegalArgumentException when [retries] is not positive.
+ */
+@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
+public fun <T> Flow<T>.retry(
+ retries: Long = Long.MAX_VALUE,
+ predicate: suspend (cause: Throwable) -> Boolean = { true }
+): Flow<T> {
+ require(retries > 0) { "Expected positive amount of retries, but had $retries" }
+ return retryWhen { cause, attempt -> attempt < retries && predicate(cause) }
+}
+
+@FlowPreview
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "binary compatibility with retries: Int preview version")
+public fun <T> Flow<T>.retry(
+ retries: Int = Int.MAX_VALUE,
+ predicate: (Throwable) -> Boolean = { true }
+): Flow<T> {
+ require(retries > 0) { "Expected positive amount of retries, but had $retries" }
+ return retryWhen { cause, attempt -> predicate(cause) && attempt < retries }
+}
+
+/**
+ * Retries collection of the given flow when an exception occurs in the upstream flow and the
+ * [predicate] returns true. The predicate also receives an `attempt` number as parameter,
+ * starting from zero on the initial call. This operator is *transparent* to exceptions that occur
+ * in downstream flow and does not retry on exceptions that are thrown to cancel the flow.
+ *
+ * For example, the following call retries the flow forever if the error is caused by `IOException`, but
+ * stops after 3 retries on any other exception:
+ *
+ * ```
+ * flow.retryWhen { cause, attempt -> cause is IOException || attempt < 3 }
+ * ```
+ *
+ * To implement a simple retry logic with a limit on the number of retries use [retry] operator.
+ *
+ * Similarly to [catch] operator, the [predicate] code has [FlowCollector] as a receiver and can
+ * [emit][FlowCollector.emit] values downstream.
+ * The [predicate] is a suspending function, so it can be used to introduce delay before retry, for example:
+ *
+ * ```
+ * flow.retryWhen { cause, attempt ->
+ * if (cause is IOException) { // retry on IOException
+ * emit(RetryWrapperValue(e))
+ * delay(1000) // delay for one second before retry
+ * true
+ * } else { // do not retry otherwise
+ * false
+ * }
+ * }
+ * ```
+ *
+ * See [catch] for more details.
+ */
+@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
+public fun <T> Flow<T>.retryWhen(predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T> =
+ flow {
+ var attempt = 0L
+ var shallRetry: Boolean
+ do {
+ shallRetry = false
+ val cause = catchImpl(this)
+ if (cause != null) {
+ if (predicate(cause, attempt)) {
+ shallRetry = true
+ attempt++
+ } else {
+ throw cause
+ }
+ }
+ } while (shallRetry)
+ }
+
+// Return exception from upstream or null
+internal suspend fun <T> Flow<T>.catchImpl(
+ collector: FlowCollector<T>
+): Throwable? {
+ var fromDownstream: Throwable? = null
+ try {
+ collect {
+ try {
+ collector.emit(it)
+ } catch (e: Throwable) {
+ fromDownstream = e
+ throw e
+ }
+ }
+ } catch (e: Throwable) {
+ /*
+ * First check ensures that we catch an original exception, not one rethrown by an operator.
+ * Seconds check ignores cancellation causes, they cannot be caught.
+ */
+ if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {
+ throw e // Rethrow exceptions from downstream and cancellation causes
+ } else {
+ return e // not from downstream
+ }
+ }
+ return null
+}
+
+private fun Throwable.isCancellationCause(coroutineContext: CoroutineContext): Boolean {
+ val job = coroutineContext[Job]
+ if (job == null || !job.isCancelled) return false
+ return isSameExceptionAs(job.getCancellationException())
+}
+
+private fun Throwable.isSameExceptionAs(other: Throwable?): Boolean =
+ other != null && unwrap(other) == unwrap(this)
+
+
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
new file mode 100644
index 00000000..7f638f98
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.jvm.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Returns a flow that ignores first [count] elements.
+ * Throws [IllegalArgumentException] if [count] is negative.
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.drop(count: Int): Flow<T> {
+ require(count >= 0) { "Drop count should be non-negative, but had $count" }
+ return flow {
+ var skipped = 0
+ collect { value ->
+ if (skipped >= count) emit(value) else ++skipped
+ }
+ }
+}
+
+/**
+ * Returns a flow containing all elements except first elements that satisfy the given predicate.
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.dropWhile(predicate: suspend (T) -> Boolean): Flow<T> = flow {
+ var matched = false
+ collect { value ->
+ if (matched) {
+ emit(value)
+ } else if (!predicate(value)) {
+ matched = true
+ emit(value)
+ }
+ }
+}
+
+/**
+ * Returns a flow that contains first [count] elements.
+ * When [count] elements are consumed, the original flow is cancelled.
+ * Throws [IllegalArgumentException] if [count] is not positive.
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.take(count: Int): Flow<T> {
+ require(count > 0) { "Requested element count $count should be positive" }
+ return flow {
+ var consumed = 0
+ try {
+ collect { value ->
+ emit(value)
+ if (++consumed == count) {
+ throw AbortFlowException()
+ }
+ }
+ } catch (e: AbortFlowException) {
+ // Nothing, bail out
+ }
+ }
+}
+
+/**
+ * Returns a flow that contains first elements satisfying the given [predicate].
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.takeWhile(predicate: suspend (T) -> Boolean): Flow<T> = flow {
+ try {
+ collect { value ->
+ if (predicate(value)) emit(value)
+ else throw AbortFlowException()
+ }
+ } catch (e: AbortFlowException) {
+ // Nothing, bail out
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
new file mode 100644
index 00000000..dccc1cd8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+@file:Suppress("unused")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.internal.*
+import kotlin.jvm.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Name of the property that defines the value of [DEFAULT_CONCURRENCY].
+ */
+@FlowPreview
+public const val DEFAULT_CONCURRENCY_PROPERTY_NAME = "kotlinx.coroutines.flow.defaultConcurrency"
+
+/**
+ * Default concurrency limit that is used by [flattenMerge] and [flatMapMerge] operators.
+ * It is 16 by default and can be changed on JVM using [DEFAULT_CONCURRENCY_PROPERTY_NAME] property.
+ */
+@FlowPreview
+public val DEFAULT_CONCURRENCY = systemProp(DEFAULT_CONCURRENCY_PROPERTY_NAME,
+ 16, 1, Int.MAX_VALUE
+)
+
+/**
+ * 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].
+ *
+ * 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.
+ */
+@FlowPreview
+public fun <T, R> Flow<T>.flatMapConcat(transform: suspend (value: T) -> Flow<R>): Flow<R> =
+ map(transform).flattenConcat()
+
+/**
+ * Transforms elements emitted by the original flow by applying [transform], that returns another flow,
+ * and then merging and flattening these flows.
+ *
+ * This operator calls [transform] *sequentially* and then merges the resulting flows with a [concurrency]
+ * limit on the number of concurrently collected flows.
+ * It is a shortcut for `map(transform).flattenMerge(concurrency)`.
+ * See [flattenMerge] for details.
+ *
+ * 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.
+ *
+ * ### Operator fusion
+ *
+ * Applications of [flowOn], [buffer], [produceIn], and [broadcastIn] _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
+ * at the same time. By default it is equal to [DEFAULT_CONCURRENCY].
+ */
+@FlowPreview
+public fun <T, R> Flow<T>.flatMapMerge(
+ concurrency: Int = DEFAULT_CONCURRENCY,
+ transform: suspend (value: T) -> Flow<R>
+): Flow<R> =
+ map(transform).flattenMerge(concurrency)
+
+/**
+ * Flattens the given flow of flows into a single flow in a sequentially manner, without interleaving nested flows.
+ * This method is conceptually identical to `flattenMerge(concurrency = 1)` but has faster implementation.
+ *
+ * Inner flows are collected by this operator *sequentially*.
+ */
+@FlowPreview
+public fun <T> Flow<Flow<T>>.flattenConcat(): Flow<T> = flow {
+ collect { value -> emitAll(value) }
+}
+
+/**
+ * Flattens the given flow of flows into a single flow with a [concurrency] limit on the number of
+ * concurrently collected flows.
+ *
+ * If [concurrency] is more than 1, then inner flows are be collected by this operator *concurrently*.
+ * With `concurrency == 1` this operator is identical to [flattenConcat].
+ *
+ * ### Operator fusion
+ *
+ * Applications of [flowOn], [buffer], [produceIn], and [broadcastIn] _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
+ * at the same time. By default it is equal to [DEFAULT_CONCURRENCY].
+ */
+@FlowPreview
+public fun <T> Flow<Flow<T>>.flattenMerge(concurrency: Int = DEFAULT_CONCURRENCY): Flow<T> {
+ require(concurrency > 0) { "Expected positive concurrency level, but had $concurrency" }
+ return if (concurrency == 1) flattenConcat() else ChannelFlowMerge(this, concurrency)
+}
+
+/**
+ * Returns a flow that produces element by [transform] function every time the original flow emits a value.
+ * When the original flow emits a new value, the previous `transform` block is cancelled, thus the name `transformLatest`.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.transformLatest { value ->
+ * emit(value)
+ * delay(200)
+ * emit(value + "_last")
+ * }
+ * ```
+ * produces `a b b_last`.
+ *
+ * This operator is [buffered][buffer] by default
+ * and size of its output buffer can be changed by applying subsequent [buffer] operator.
+ */
+@ExperimentalCoroutinesApi
+public fun <T, R> Flow<T>.transformLatest(@BuilderInference transform: suspend FlowCollector<R>.(value: T) -> Unit): Flow<R> =
+ ChannelFlowTransformLatest(transform, this)
+
+/**
+ * Returns a flow that switches to a new flow produced by [transform] function every time the original flow emits a value.
+ * When the original flow emits a new value, the previous flow produced by `transform` block is cancelled.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.flatMapLatest { value ->
+ * flow {
+ * emit(value)
+ * delay(200)
+ * emit(value + "_last")
+ * }
+ * }
+ * ```
+ * produces `a b b_last`
+ *
+ * This operator is [buffered][buffer] by default and size of its output buffer can be changed by applying subsequent [buffer] operator.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T, R> Flow<T>.flatMapLatest(@BuilderInference crossinline transform: suspend (value: T) -> Flow<R>): Flow<R> =
+ transformLatest { emitAll(transform(it)) }
+
+/**
+ * Returns a flow that emits elements from the original flow transformed by [transform] function.
+ * When the original flow emits a new value, computation of the [transform] block for previous value is cancelled.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.mapLatest { value ->
+ * println("Started computing $value")
+ * delay(200)
+ * "Computed $value"
+ * }
+ * ```
+ * will print "Started computing 1" and "Started computing 2", but the resulting flow will contain only "Computed 2" value.
+ *
+ * This operator is [buffered][buffer] by default and size of its output buffer can be changed by applying subsequent [buffer] operator.
+ */
+@ExperimentalCoroutinesApi
+public fun <T, R> Flow<T>.mapLatest(@BuilderInference transform: suspend (value: T) -> R): Flow<R> =
+ transformLatest { emit(transform(it)) }
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
new file mode 100644
index 00000000..da5e288a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+@file:Suppress("UNCHECKED_CAST")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.jvm.*
+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].
+ */
+public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
+ if (predicate(value)) return@transform emit(value)
+}
+
+/**
+ * Returns a flow containing only values of the original flow that do not match the given [predicate].
+ */
+public inline fun <T> Flow<T>.filterNot(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
+ if (!predicate(value)) return@transform emit(value)
+}
+
+/**
+ * Returns a flow containing only values that are instances of specified type [R].
+ */
+@Suppress("UNCHECKED_CAST")
+public inline fun <reified R> Flow<*>.filterIsInstance(): Flow<R> = filter { it is R } as Flow<R>
+
+/**
+ * Returns a flow containing only values of the original flow that are not null.
+ */
+public fun <T: Any> Flow<T?>.filterNotNull(): Flow<T> = transform<T?, T> { value ->
+ if (value != null) return@transform emit(value)
+}
+
+/**
+ * Returns a flow containing the results of applying the given [transform] function to each value of the original flow.
+ */
+public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
+ return@transform emit(transform(value))
+}
+
+/**
+ * Returns a flow that contains only non-null results of applying the given [transform] function to each value of the original flow.
+ */
+public inline fun <T, R: Any> Flow<T>.mapNotNull(crossinline transform: suspend (value: T) -> R?): Flow<R> = transform { value ->
+ val transformed = transform(value) ?: return@transform
+ return@transform emit(transformed)
+}
+
+/**
+ * Returns a flow that wraps each element into [IndexedValue], containing value and its index (starting from zero).
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.withIndex(): Flow<IndexedValue<T>> = flow {
+ var index = 0
+ collect { value ->
+ emit(IndexedValue(checkIndexOverflow(index++), value))
+ }
+}
+
+/**
+ * Returns a flow which performs the given [action] on each value of the original flow.
+ */
+public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> = transform { value ->
+ action(value)
+ return@transform emit(value)
+}
+
+/**
+ * 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).scan(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 {
+ var accumulator: R = initial
+ emit(accumulator)
+ collect { value ->
+ accumulator = operation(accumulator, value)
+ emit(accumulator)
+ }
+}
+
+/**
+ * Reduces the given flow with [operation], emitting every intermediate result, including initial value.
+ * The first element is taken as initial value for operation accumulator.
+ * This operator has a sibling with initial value -- [scan].
+ *
+ * For example:
+ * ```
+ * flowOf(1, 2, 3, 4).scanReduce { (v1, v2) -> v1 + v2 }.toList()
+ * ```
+ * will produce `[1, 3, 6, 10]`
+ */
+@ExperimentalCoroutinesApi
+public fun <T> Flow<T>.scanReduce(operation: suspend (accumulator: T, value: T) -> T): Flow<T> = flow {
+ var accumulator: Any? = NULL
+ collect { value ->
+ accumulator = if (accumulator === NULL) {
+ value
+ } else {
+ operation(accumulator as T, value)
+ }
+ emit(accumulator as T)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt b/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt
new file mode 100644
index 00000000..ebc1dcd9
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+@file:Suppress("UNCHECKED_CAST", "NON_APPLICABLE_CALL_FOR_BUILDER_INFERENCE") // KT-32203
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.jvm.*
+import kotlinx.coroutines.flow.flow as safeFlow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Returns a [Flow] whose values are generated with [transform] function by combining
+ * the most recently emitted values by each flow.
+ *
+ * It can be demonstrated with the following example:
+ * ```
+ * val flow = flowOf(1, 2).delayEach(10)
+ * val flow2 = flowOf("a", "b", "c").delayEach(15)
+ * flow.combine(flow2) { i, s -> i.toString() + s }.collect {
+ * println(it) // Will print "1a 2a 2b 2c"
+ * }
+ * ```
+ *
+ * This function is a shorthand for `flow.combineTransform(flow2) { a, b -> emit(transform(a, b)) }
+ */
+@JvmName("flowCombine")
+@ExperimentalCoroutinesApi
+public fun <T1, T2, R> Flow<T1>.combine(flow: Flow<T2>, transform: suspend (a: T1, b: T2) -> R): Flow<R> = flow {
+ combineTransformInternal(this@combine, flow) { a, b ->
+ emit(transform(a, b))
+ }
+}
+
+/**
+ * Returns a [Flow] whose values are generated with [transform] function by combining
+ * the most recently emitted values by each flow.
+ *
+ * It can be demonstrated with the following example:
+ * ```
+ * val flow = flowOf(1, 2).delayEach(10)
+ * val flow2 = flowOf("a", "b", "c").delayEach(15)
+ * combine(flow, flow2) { i, s -> i.toString() + s }.collect {
+ * println(it) // Will print "1a 2a 2b 2c"
+ * }
+ * ```
+ *
+ * This function is a shorthand for `combineTransform(flow, flow2) { a, b -> emit(transform(a, b)) }
+ */
+@ExperimentalCoroutinesApi
+public fun <T1, T2, R> combine(flow: Flow<T1>, flow2: Flow<T2>, transform: suspend (a: T1, b: T2) -> R): Flow<R> =
+ flow.combine(flow2, transform)
+
+/**
+ * Returns a [Flow] whose values are generated by [transform] function that process the most recently emitted values by each flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ *
+ * Its usage can be demonstrated with the following example:
+ * ```
+ * val flow = requestFlow()
+ * val flow2 = searchEngineFlow()
+ * flow.combineTransform(flow2) { request, searchEngine ->
+ * emit("Downloading in progress")
+ * val result = download(request, searchEngine)
+ * emit(result)
+ * }
+ * ```
+ */
+@JvmName("flowCombineTransform")
+@ExperimentalCoroutinesApi
+public fun <T1, T2, R> Flow<T1>.combineTransform(
+ flow: Flow<T2>,
+ @BuilderInference transform: suspend FlowCollector<R>.(a: T1, b: T2) -> Unit
+): Flow<R> = safeFlow {
+ combineTransformInternal(this@combineTransform, flow) { a, b ->
+ transform(a, b)
+ }
+}
+
+/**
+ * Returns a [Flow] whose values are generated by [transform] function that process the most recently emitted values by each flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ *
+ * Its usage can be demonstrated with the following example:
+ * ```
+ * val flow = requestFlow()
+ * val flow2 = searchEngineFlow()
+ * combineTransform(flow, flow2) { request, searchEngine ->
+ * emit("Downloading in progress")
+ * val result = download(request, searchEngine)
+ * emit(result)
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public fun <T1, T2, R> combineTransform(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ @BuilderInference transform: suspend FlowCollector<R>.(a: T1, b: T2) -> Unit
+): Flow<R> = combineTransform(flow, flow2) { args: Array<*> ->
+ transform(
+ args[0] as T1,
+ args[1] as T2
+ )
+}
+
+/**
+ * Returns a [Flow] whose values are generated with [transform] function by combining
+ * the most recently emitted values by each flow.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T1, T2, T3, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ @BuilderInference crossinline transform: suspend (T1, T2, T3) -> R
+): Flow<R> = combine(flow, flow2, flow3) { args: Array<*> ->
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3
+ )
+}
+
+/**
+ * Returns a [Flow] whose values are generated by [transform] function that process the most recently emitted values by each flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T1, T2, T3, R> combineTransform(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(T1, T2, T3) -> Unit
+): Flow<R> = combineTransform(flow, flow2, flow3) { args: Array<*> ->
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3
+ )
+}
+
+/**
+ * Returns a [Flow] whose values are generated with [transform] function by combining
+ * the most recently emitted values by each flow.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T1, T2, T3, T4, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ crossinline transform: suspend (T1, T2, T3, T4) -> R
+): Flow<R> = combine(flow, flow2, flow3, flow4) { args: Array<*> ->
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4
+ )
+}
+
+/**
+ * Returns a [Flow] whose values are generated by [transform] function that process the most recently emitted values by each flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T1, T2, T3, T4, R> combineTransform(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(T1, T2, T3, T4) -> Unit
+): Flow<R> = combineTransform(flow, flow2, flow3, flow4) { args: Array<*> ->
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4
+ )
+}
+
+/**
+ * Returns a [Flow] whose values are generated with [transform] function by combining
+ * the most recently emitted values by each flow.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T1, T2, T3, T4, T5, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5) -> R
+): Flow<R> = combine(flow, flow2, flow3, flow4, flow5) { args: Array<*> ->
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5
+ )
+}
+
+/**
+ * Returns a [Flow] whose values are generated by [transform] function that process the most recently emitted values by each flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T1, T2, T3, T4, T5, R> combineTransform(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(T1, T2, T3, T4, T5) -> Unit
+): Flow<R> = combineTransform(flow, flow2, flow3, flow4, flow5) { args: Array<*> ->
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5
+ )
+}
+
+/**
+ * Returns a [Flow] whose values are generated with [transform] function by combining
+ * the most recently emitted values by each flow.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <reified T, R> combine(
+ vararg flows: Flow<T>,
+ crossinline transform: suspend (Array<T>) -> R
+): Flow<R> = flow {
+ combineInternal(flows, { arrayOfNulls(flows.size) }, { emit(transform(it)) })
+}
+
+/**
+ * Returns a [Flow] whose values are generated by [transform] function that process the most recently emitted values by each flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <reified T, R> combineTransform(
+ vararg flows: Flow<T>,
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(Array<T>) -> Unit
+): Flow<R> = safeFlow {
+ combineInternal(flows, { arrayOfNulls(flows.size) }, { transform(it) })
+}
+
+/**
+ * Returns a [Flow] whose values are generated with [transform] function by combining
+ * the most recently emitted values by each flow.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <reified T, R> combine(
+ flows: Iterable<Flow<T>>,
+ crossinline transform: suspend (Array<T>) -> R
+): Flow<R> {
+ val flowArray = flows.toList().toTypedArray()
+ return flow {
+ combineInternal(
+ flowArray,
+ arrayFactory = { arrayOfNulls(flowArray.size) },
+ transform = { emit(transform(it)) })
+ }
+}
+
+/**
+ * Returns a [Flow] whose values are generated by [transform] function that process the most recently emitted values by each flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ */
+@ExperimentalCoroutinesApi
+public inline fun <reified T, R> combineTransform(
+ flows: Iterable<Flow<T>>,
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(Array<T>) -> Unit
+): Flow<R> {
+ val flowArray = flows.toList().toTypedArray()
+ return safeFlow {
+ combineInternal(flowArray, { arrayOfNulls(flowArray.size) }, { transform(it) })
+ }
+}
+
+/**
+ * Zips values from the current flow (`this`) with [other] flow using provided [transform] function applied to each pair of values.
+ * The resulting flow completes as soon as one of the flows completes and cancel is called on the remaining flow.
+ *
+ * It can be demonstrated with the following example:
+ * ```
+ * val flow = flowOf(1, 2, 3).delayEach(10)
+ * val flow2 = flowOf("a", "b", "c", "d").delayEach(15)
+ * flow.zip(flow2) { i, s -> i.toString() + s }.collect {
+ * println(it) // Will print "1a 2b 3c"
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public fun <T1, T2, R> Flow<T1>.zip(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = zipImpl(this, other, transform)
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
new file mode 100644
index 00000000..c9480f99
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.jvm.*
+
+/**
+ * Terminal flow operator that collects the given flow but ignores all emitted values.
+ * If any exception occurs during collect or in the provided flow, this exception is rethrown from this method.
+ *
+ * It is a shorthand for `collect {}`.
+ *
+ * This operator is usually used with [onEach], [onCompletion] and [catch] operators to process all emitted values and
+ * handle an exception that might occur in the upstream flow or during processing, for example:
+ *
+ * ```
+ * flow
+ * .onEach { value -> process(value) }
+ * .catch { e -> handleException(e) }
+ * .collect() // trigger collection of the flow
+ * ```
+ */
+public suspend fun Flow<*>.collect() = collect(NopCollector)
+
+/**
+ * Terminal flow operator that [launches][launch] the [collection][collect] of the given flow in the [scope].
+ * It is a shorthand for `scope.launch { flow.collect() }`.
+ *
+ * This operator is usually used with [onEach], [onCompletion] and [catch] operators to process all emitted values
+ * handle an exception that might occur in the upstream flow or during processing, for example:
+ *
+ * ```
+ * flow
+ * .onEach { value -> updateUi(value) }
+ * .onCompletion { cause -> updateUi(if (cause == null) "Done" else "Failed") }
+ * .catch { cause -> LOG.error("Exception: $cause") }
+ * .launchIn(uiScope)
+ * ```
+ *
+ * Note that resulting value of [launchIn] is not used the provided scope takes care of cancellation.
+ */
+@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
+public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
+ collect() // tail-call
+}
+
+/**
+ * Terminal flow operator that collects the given flow with a provided [action].
+ * If any exception occurs during collect or in the provided flow, this exception is rethrown from this method.
+ *
+ * Example of use:
+ *
+ * ```
+ * val flow = getMyEvents()
+ * try {
+ * flow.collect { value ->
+ * println("Received $value")
+ * }
+ * println("My events are consumed successfully")
+ * } catch (e: Throwable) {
+ * println("Exception from the flow: $e")
+ * }
+ * ```
+ */
+public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
+ collect(object : FlowCollector<T> {
+ override suspend fun emit(value: T) = action(value)
+ })
+
+/**
+ * Terminal flow operator that collects the given flow with a provided [action] that takes the index of an element (zero-based) and the element.
+ * If any exception occurs during collect or in the provided flow, this exception is rethrown from this method.
+ *
+ * See also [collect] and [withIndex].
+ */
+@ExperimentalCoroutinesApi
+public suspend inline fun <T> Flow<T>.collectIndexed(crossinline action: suspend (index: Int, value: T) -> Unit): Unit =
+ collect(object : FlowCollector<T> {
+ private var index = 0
+ override suspend fun emit(value: T) = action(checkIndexOverflow(index++), value)
+ })
+
+/**
+ * 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.
+ *
+ * It can be demonstrated by the following example:
+ *
+ * ```
+ * flow {
+ * emit(1)
+ * delay(50)
+ * emit(2)
+ * }.collectLatest { value ->
+ * println("Collecting $value")
+ * delay(100) // Emulate work
+ * println("$value collected")
+ * }
+ * ```
+ *
+ * prints "Collecting 1, Collecting 2, 2 collected"
+ */
+@ExperimentalCoroutinesApi
+public suspend fun <T> Flow<T>.collectLatest(action: suspend (value: T) -> Unit) {
+ /*
+ * Implementation note:
+ * buffer(0) is inserted here to fulfil user's expectations in sequential usages, e.g.:
+ * ```
+ * flowOf(1, 2, 3).collectLatest {
+ * delay(1)
+ * println(it) // Expect only 3 to be printed
+ * }
+ * ```
+ *
+ * It's not the case for intermediate operators which users mostly use for interactive UI,
+ * where performance of dispatch is more important.
+ */
+ mapLatest(action).buffer(0).collect()
+}
+
+/**
+ * Collects all the values from the given [flow] and emits them to the collector.
+ * It is a shorthand for `flow.collect { value -> emit(value) }`.
+ */
+@ExperimentalCoroutinesApi
+public suspend inline fun <T> FlowCollector<T>.emitAll(flow: Flow<T>) = 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
new file mode 100644
index 00000000..e07be616
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlin.jvm.*
+
+/**
+ * Collects given flow into a [destination]
+ */
+public suspend fun <T> Flow<T>.toList(destination: MutableList<T> = ArrayList()): List<T> = toCollection(destination)
+
+/**
+ * Collects given flow into a [destination]
+ */
+public suspend fun <T> Flow<T>.toSet(destination: MutableSet<T> = LinkedHashSet()): Set<T> = toCollection(destination)
+
+/**
+ * Collects given flow into a [destination]
+ */
+public suspend fun <T, C : MutableCollection<in T>> Flow<T>.toCollection(destination: C): C {
+ collect { value ->
+ destination.add(value)
+ }
+ return destination
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt
new file mode 100644
index 00000000..d57dfdef
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Returns the number of elements in this flow.
+ */
+@ExperimentalCoroutinesApi
+public suspend fun <T> Flow<T>.count(): Int {
+ var i = 0
+ collect {
+ ++i
+ }
+
+ return i
+}
+
+/**
+ * Returns the number of elements matching the given predicate.
+ */
+@ExperimentalCoroutinesApi
+public suspend fun <T> Flow<T>.count(predicate: suspend (T) -> Boolean): Int {
+ var i = 0
+ collect { value ->
+ if (predicate(value)) {
+ ++i
+ }
+ }
+
+ return i
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
new file mode 100644
index 00000000..875e6b66
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+@file:Suppress("UNCHECKED_CAST")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+import kotlin.jvm.*
+
+/**
+ * Accumulates value starting with the first element and applying [operation] to current accumulator value and each element.
+ * Throws [UnsupportedOperationException] if flow was empty.
+ */
+@ExperimentalCoroutinesApi
+public suspend fun <S, T : S> Flow<T>.reduce(operation: suspend (accumulator: S, value: T) -> S): S {
+ var accumulator: Any? = NULL
+
+ collect { value ->
+ accumulator = if (accumulator !== NULL) {
+ @Suppress("UNCHECKED_CAST")
+ operation(accumulator as S, value)
+ } else {
+ value
+ }
+ }
+
+ if (accumulator === NULL) throw UnsupportedOperationException("Empty flow can't be reduced")
+ @Suppress("UNCHECKED_CAST")
+ return accumulator as S
+}
+
+/**
+ * Accumulates value starting with [initial] value and applying [operation] current accumulator value and each element
+ */
+@ExperimentalCoroutinesApi
+public suspend inline fun <T, R> Flow<T>.fold(
+ initial: R,
+ crossinline operation: suspend (acc: R, value: T) -> R
+): R {
+ var accumulator = initial
+ collect { value ->
+ accumulator = operation(accumulator, value)
+ }
+ return accumulator
+}
+
+/**
+ * The terminal operator, that awaits for one and only one value to be published.
+ * Throws [NoSuchElementException] for empty flow and [IllegalStateException] for flow
+ * that contains more than one element.
+ */
+public suspend fun <T> Flow<T>.single(): T {
+ var result: Any? = NULL
+ collect { value ->
+ if (result !== NULL) error("Expected only one element")
+ result = value
+ }
+
+ if (result === NULL) throw NoSuchElementException("Expected at least one element")
+ @Suppress("UNCHECKED_CAST")
+ return result as T
+}
+
+/**
+ * The terminal operator, that awaits for one and only one value to be published.
+ * Throws [IllegalStateException] for flow that contains more than one element.
+ */
+public suspend fun <T: Any> Flow<T>.singleOrNull(): T? {
+ var result: T? = null
+ collect { value ->
+ if (result != null) error("Expected only one element")
+ result = value
+ }
+
+ return result
+}
+
+/**
+ * The terminal operator that returns the first element emitted by the flow and then cancels flow's collection.
+ * Throws [NoSuchElementException] if the flow was empty.
+ */
+public suspend fun <T> Flow<T>.first(): T {
+ var result: Any? = NULL
+ try {
+ collect { value ->
+ result = value
+ throw AbortFlowException()
+ }
+ } catch (e: AbortFlowException) {
+ // Do nothing
+ }
+
+ if (result === NULL) throw NoSuchElementException("Expected at least one element")
+ return result as T
+}
+
+/**
+ * The terminal operator that returns the first element emitted by the flow matching the given [predicate] and then cancels flow's collection.
+ * Throws [NoSuchElementException] if the flow has not contained elements matching the [predicate].
+ */
+public suspend fun <T> Flow<T>.first(predicate: suspend (T) -> Boolean): T {
+ var result: Any? = NULL
+ try {
+ collect { value ->
+ if (predicate(value)) {
+ result = value
+ throw AbortFlowException()
+ }
+ }
+ } catch (e: AbortFlowException) {
+ // Do nothing
+ }
+
+ if (result === NULL) throw NoSuchElementException("Expected at least one element matching the predicate $predicate")
+ return result as T
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt b/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt
new file mode 100644
index 00000000..61a32338
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+internal open class ArrayQueue<T : Any> {
+ private var elements = arrayOfNulls<Any>(16)
+ private var head = 0
+ private var tail = 0
+
+ val isEmpty: Boolean get() = head == tail
+
+ public fun addLast(element: T) {
+ elements[tail] = element
+ tail = (tail + 1) and elements.size - 1
+ if (tail == head) ensureCapacity()
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ public fun removeFirstOrNull(): T? {
+ if (head == tail) return null
+ val element = elements[head]
+ elements[head] = null
+ head = (head + 1) and elements.size - 1
+ return element as T
+ }
+
+ public fun clear() {
+ head = 0
+ tail = 0
+ elements = arrayOfNulls(elements.size)
+ }
+
+ private fun ensureCapacity() {
+ val currentSize = elements.size
+ val newCapacity = currentSize shl 1
+ val newElements = arrayOfNulls<Any>(newCapacity)
+ elements.copyInto(
+ destination = newElements,
+ startIndex = head
+ )
+ elements.copyInto(
+ destination = newElements,
+ destinationOffset = elements.size - head,
+ endIndex = head
+ )
+ elements = newElements
+ head = 0
+ tail = currentSize
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/Atomic.kt b/kotlinx-coroutines-core/common/src/internal/Atomic.kt
new file mode 100644
index 00000000..bc528153
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/Atomic.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.*
+
+/**
+ * The most abstract operation that can be in process. Other threads observing an instance of this
+ * class in the fields of their object shall invoke [perform] to help.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+public abstract class OpDescriptor {
+ /**
+ * Returns `null` is operation was performed successfully or some other
+ * object that indicates the failure reason.
+ */
+ abstract fun perform(affected: Any?): Any?
+}
+
+@SharedImmutable
+private val NO_DECISION: Any = Symbol("NO_DECISION")
+
+/**
+ * Descriptor for multi-word atomic operation.
+ * Based on paper
+ * ["A Practical Multi-Word Compare-and-Swap Operation"](https://www.cl.cam.ac.uk/research/srg/netos/papers/2002-casn.pdf)
+ * by Timothy L. Harris, Keir Fraser and Ian A. Pratt.
+ *
+ * Note: parts of atomic operation must be globally ordered. Otherwise, this implementation will produce
+ * `StackOverflowError`.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+public abstract class AtomicOp<in T> : OpDescriptor() {
+ private val _consensus = atomic<Any?>(NO_DECISION)
+
+ val isDecided: Boolean get() = _consensus.value !== NO_DECISION
+
+ fun tryDecide(decision: Any?): Boolean {
+ assert { decision !== NO_DECISION }
+ return _consensus.compareAndSet(NO_DECISION, decision)
+ }
+
+ private fun decide(decision: Any?): Any? = if (tryDecide(decision)) decision else _consensus.value
+
+ abstract fun prepare(affected: T): Any? // `null` if Ok, or failure reason
+
+ abstract fun complete(affected: T, failure: Any?) // failure != null if failed to prepare op
+
+ // returns `null` on success
+ @Suppress("UNCHECKED_CAST")
+ final override fun perform(affected: Any?): Any? {
+ // make decision on status
+ var decision = this._consensus.value
+ if (decision === NO_DECISION) {
+ decision = decide(prepare(affected as T))
+ }
+
+ complete(affected as T, decision)
+ return decision
+ }
+}
+
+/**
+ * A part of multi-step atomic operation [AtomicOp].
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+public abstract class AtomicDesc {
+ abstract fun prepare(op: AtomicOp<*>): Any? // returns `null` if prepared successfully
+ abstract fun complete(op: AtomicOp<*>, failure: Any?) // decision == null if success
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
new file mode 100644
index 00000000..6b096f04
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+/**
+ * Special kind of list intended to be used as collection of subscribers in `ArrayBroadcastChannel`
+ * On JVM it's CopyOnWriteList and on JS it's MutableList.
+ *
+ * Note that this alias is intentionally not named as CopyOnWriteList to avoid accidental misusage outside of ArrayBroadcastChannel
+ */
+internal typealias SubscribersList<E> = MutableList<E>
+
+internal expect fun <E> subscriberList(): SubscribersList<E>
+
+internal expect class ReentrantLock() {
+ fun tryLock(): Boolean
+ fun unlock(): Unit
+}
+
+internal expect inline fun <T> ReentrantLock.withLock(action: () -> T): T
+
+internal expect fun <E> identitySet(expectedSize: Int): MutableSet<E>
+
+@ExperimentalMultiplatform
+@OptionalExpectation
+internal expect annotation class SharedImmutable()
diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
new file mode 100644
index 00000000..fc1c72f0
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public expect open class LockFreeLinkedListNode() {
+ public val isRemoved: Boolean
+ public val nextNode: LockFreeLinkedListNode
+ public val prevNode: LockFreeLinkedListNode
+ public fun addLast(node: LockFreeLinkedListNode)
+ public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean
+ public inline fun addLastIf(node: LockFreeLinkedListNode, crossinline condition: () -> Boolean): Boolean
+ public inline fun addLastIfPrev(
+ node: LockFreeLinkedListNode,
+ predicate: (LockFreeLinkedListNode) -> Boolean
+ ): Boolean
+
+ public inline fun addLastIfPrevAndIf(
+ node: LockFreeLinkedListNode,
+ predicate: (LockFreeLinkedListNode) -> Boolean, // prev node predicate
+ crossinline condition: () -> Boolean // atomically checked condition
+ ): Boolean
+
+ public open fun remove(): Boolean
+
+ /**
+ * Helps fully finish [remove] operation, must be invoked after [remove] if needed.
+ * Ensures that traversing the list via prev pointers sees this node as removed.
+ * No-op on JS
+ */
+ public fun helpRemove()
+ public fun removeFirstOrNull(): LockFreeLinkedListNode?
+ public inline fun <reified T> removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T?
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public expect open class LockFreeLinkedListHead() : LockFreeLinkedListNode {
+ public val isEmpty: Boolean
+ public inline fun <reified T : LockFreeLinkedListNode> forEach(block: (T) -> Unit)
+ public final override fun remove(): Boolean // Actual return type is Nothing, KT-27534
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public expect open class AddLastDesc<T : LockFreeLinkedListNode>(
+ queue: LockFreeLinkedListNode,
+ node: T
+) : AbstractAtomicDesc {
+ val queue: LockFreeLinkedListNode
+ val node: T
+ protected override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any?
+ override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode)
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public expect open class RemoveFirstDesc<T>(queue: LockFreeLinkedListNode): AbstractAtomicDesc {
+ val queue: LockFreeLinkedListNode
+ public val result: T
+ protected open fun validatePrepared(node: T): Boolean
+ protected final override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any?
+ final override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode)
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public expect abstract class AbstractAtomicDesc : AtomicDesc {
+ final override fun prepare(op: AtomicOp<*>): Any?
+ final override fun complete(op: AtomicOp<*>, failure: Any?)
+ protected open fun failure(affected: LockFreeLinkedListNode): Any?
+ protected open fun retry(affected: LockFreeLinkedListNode, next: Any): Boolean
+ protected abstract fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? // non-null on failure
+ protected abstract fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode)
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt
new file mode 100644
index 00000000..68723104
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2016-2019 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.*
+
+private typealias Core<E> = LockFreeTaskQueueCore<E>
+
+/**
+ * Lock-free Multiply-Producer xxx-Consumer Queue for task scheduling purposes.
+ *
+ * **Note 1: This queue is NOT linearizable. It provides only quiescent consistency for its operations.**
+ * However, this guarantee is strong enough for task-scheduling purposes.
+ * In particular, the following execution is permitted for this queue, but is not permitted for a linearizable queue:
+ *
+ * ```
+ * Thread 1: addLast(1) = true, removeFirstOrNull() = null
+ * Thread 2: addLast(2) = 2 // this operation is concurrent with both operations in the first thread
+ * ```
+ *
+ * **Note 2: When this queue is used with multiple consumers (`singleConsumer == false`) this it is NOT lock-free.**
+ * In particular, consumer spins until producer finishes its operation in the case of near-empty queue.
+ * It is a very short window that could manifest itself rarely and only under specific load conditions,
+ * but it still deprives this algorithm of its lock-freedom.
+ */
+internal open class LockFreeTaskQueue<E : Any>(
+ singleConsumer: Boolean // true when there is only a single consumer (slightly faster & lock-free)
+) {
+ private val _cur = atomic(Core<E>(Core.INITIAL_CAPACITY, singleConsumer))
+
+ // Note: it is not atomic w.r.t. remove operation (remove can transiently fail when isEmpty is false)
+ val isEmpty: Boolean get() = _cur.value.isEmpty
+ val size: Int get() = _cur.value.size
+
+ fun close() {
+ _cur.loop { cur ->
+ if (cur.close()) return // closed this copy
+ _cur.compareAndSet(cur, cur.next()) // move to next
+ }
+ }
+
+ fun addLast(element: E): Boolean {
+ _cur.loop { cur ->
+ when (cur.addLast(element)) {
+ Core.ADD_SUCCESS -> return true
+ Core.ADD_CLOSED -> return false
+ Core.ADD_FROZEN -> _cur.compareAndSet(cur, cur.next()) // move to next
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun removeFirstOrNull(): E? = removeFirstOrNullIf { true }
+
+ @Suppress("UNCHECKED_CAST")
+ inline fun removeFirstOrNullIf(predicate: (E) -> Boolean): E? {
+ _cur.loop { cur ->
+ val result = cur.removeFirstOrNullIf(predicate)
+ if (result !== Core.REMOVE_FROZEN) return result as E?
+ _cur.compareAndSet(cur, cur.next())
+ }
+ }
+
+ // Used for validation in tests only
+ fun <R> map(transform: (E) -> R): List<R> = _cur.value.map(transform)
+
+ // Used for validation in tests only
+ fun isClosed(): Boolean = _cur.value.isClosed()
+}
+
+/**
+ * Lock-free Multiply-Producer xxx-Consumer Queue core.
+ * @see LockFreeTaskQueue
+ */
+internal class LockFreeTaskQueueCore<E : Any>(
+ private val capacity: Int,
+ private val singleConsumer: Boolean // true when there is only a single consumer (slightly faster)
+) {
+ private val mask = capacity - 1
+ private val _next = atomic<Core<E>?>(null)
+ private val _state = atomic(0L)
+ private val array = atomicArrayOfNulls<Any?>(capacity)
+
+ init {
+ check(mask <= MAX_CAPACITY_MASK)
+ check(capacity and mask == 0)
+ }
+
+ // Note: it is not atomic w.r.t. remove operation (remove can transiently fail when isEmpty is false)
+ val isEmpty: Boolean get() = _state.value.withState { head, tail -> head == tail }
+ val size: Int get() = _state.value.withState { head, tail -> (tail - head) and MAX_CAPACITY_MASK }
+
+ fun close(): Boolean {
+ _state.update { state ->
+ if (state and CLOSED_MASK != 0L) return true // ok - already closed
+ if (state and FROZEN_MASK != 0L) return false // frozen -- try next
+ state or CLOSED_MASK // try set closed bit
+ }
+ return true
+ }
+
+ // ADD_CLOSED | ADD_FROZEN | ADD_SUCCESS
+ fun addLast(element: E): Int {
+ _state.loop { state ->
+ if (state and (FROZEN_MASK or CLOSED_MASK) != 0L) return state.addFailReason() // cannot add
+ state.withState { head, tail ->
+ val mask = this.mask // manually move instance field to local for performance
+ // If queue is Single-Consumer then there could be one element beyond head that we cannot overwrite,
+ // so we check for full queue with an extra margin of one element
+ if ((tail + 2) and mask == head and mask) return ADD_FROZEN // overfull, so do freeze & copy
+ // If queue is Multi-Consumer then the consumer could still have not cleared element
+ // despite the above check for one free slot.
+ if (!singleConsumer && array[tail and mask].value != null) {
+ // There are two options in this situation
+ // 1. Spin-wait until consumer clears the slot
+ // 2. Freeze & resize to avoid spinning
+ // We use heuristic here to avoid memory-overallocation
+ // Freeze & reallocate when queue is small or more than half of the queue is used
+ if (capacity < MIN_ADD_SPIN_CAPACITY || (tail - head) and MAX_CAPACITY_MASK > capacity shr 1) {
+ return ADD_FROZEN
+ }
+ // otherwise spin
+ return@loop
+ }
+ val newTail = (tail + 1) and MAX_CAPACITY_MASK
+ if (_state.compareAndSet(state, state.updateTail(newTail))) {
+ // successfully added
+ array[tail and mask].value = element
+ // could have been frozen & copied before this item was set -- correct it by filling placeholder
+ var cur = this
+ while(true) {
+ if (cur._state.value and FROZEN_MASK == 0L) break // all fine -- not frozen yet
+ cur = cur.next().fillPlaceholder(tail, element) ?: break
+ }
+ return ADD_SUCCESS // added successfully
+ }
+ }
+ }
+ }
+
+ private fun fillPlaceholder(index: Int, element: E): Core<E>? {
+ val old = array[index and mask].value
+ /*
+ * addLast actions:
+ * 1) Commit tail slot
+ * 2) Write element to array slot
+ * 3) Check for array copy
+ *
+ * If copy happened between 2 and 3 then the consumer might have consumed our element,
+ * then another producer might have written its placeholder in our slot, so we should
+ * perform *unique* check that current placeholder is our to avoid overwriting another producer placeholder
+ */
+ if (old is Placeholder && old.index == index) {
+ array[index and mask].value = element
+ // we've corrected missing element, should check if that propagated to further copies, just in case
+ return this
+ }
+ // it is Ok, no need for further action
+ return null
+ }
+
+ // REMOVE_FROZEN | null (EMPTY) | E (SUCCESS)
+ fun removeFirstOrNull(): Any? = removeFirstOrNullIf { true }
+
+ // REMOVE_FROZEN | null (EMPTY) | E (SUCCESS)
+ inline fun removeFirstOrNullIf(predicate: (E) -> Boolean): Any? {
+ _state.loop { state ->
+ if (state and FROZEN_MASK != 0L) return REMOVE_FROZEN // frozen -- cannot modify
+ state.withState { head, tail ->
+ if ((tail and mask) == (head and mask)) return null // empty
+ val element = array[head and mask].value
+ if (element == null) {
+ // If queue is Single-Consumer, then element == null only when add has not finished yet
+ if (singleConsumer) return null // consider it not added yet
+ // retry (spin) until consumer adds it
+ return@loop
+ }
+ // element == Placeholder can only be when add has not finished yet
+ if (element is Placeholder) return null // consider it not added yet
+ // now we tentative know element to remove -- check predicate
+ @Suppress("UNCHECKED_CAST")
+ if (!predicate(element as E)) return null
+ // we cannot put null into array here, because copying thread could replace it with Placeholder and that is a disaster
+ val newHead = (head + 1) and MAX_CAPACITY_MASK
+ if (_state.compareAndSet(state, state.updateHead(newHead))) {
+ // Array could have been copied by another thread and it is perfectly fine, since only elements
+ // between head and tail were copied and there are no extra steps we should take here
+ array[head and mask].value = null // now can safely put null (state was updated)
+ return element // successfully removed in fast-path
+ }
+ // Multi-Consumer queue must retry this loop on CAS failure (another consumer might have removed element)
+ if (!singleConsumer) return@loop
+ // Single-consumer queue goes to slow-path for remove in case of interference
+ var cur = this
+ while (true) {
+ @Suppress("UNUSED_VALUE")
+ cur = cur.removeSlowPath(head, newHead) ?: return element
+ }
+ }
+ }
+ }
+
+ private fun removeSlowPath(oldHead: Int, newHead: Int): Core<E>? {
+ _state.loop { state ->
+ state.withState { head, _ ->
+ assert { head == oldHead } // "This queue can have only one consumer"
+ if (state and FROZEN_MASK != 0L) {
+ // state was already frozen, so removed element was copied to next
+ return next() // continue to correct head in next
+ }
+ if (_state.compareAndSet(state, state.updateHead(newHead))) {
+ array[head and mask].value = null // now can safely put null (state was updated)
+ return null
+ }
+ }
+ }
+ }
+
+ fun next(): LockFreeTaskQueueCore<E> = allocateOrGetNextCopy(markFrozen())
+
+ private fun markFrozen(): Long =
+ _state.updateAndGet { state ->
+ if (state and FROZEN_MASK != 0L) return state // already marked
+ state or FROZEN_MASK
+ }
+
+ private fun allocateOrGetNextCopy(state: Long): Core<E> {
+ _next.loop { next ->
+ if (next != null) return next // already allocated & copied
+ _next.compareAndSet(null, allocateNextCopy(state))
+ }
+ }
+
+ private fun allocateNextCopy(state: Long): Core<E> {
+ val next = LockFreeTaskQueueCore<E>(capacity * 2, singleConsumer)
+ state.withState { head, tail ->
+ var index = head
+ while (index and mask != tail and mask) {
+ // replace nulls with placeholders on copy
+ val value = array[index and mask].value ?: Placeholder(index)
+ next.array[index and next.mask].value = value
+ index++
+ }
+ next._state.value = state wo FROZEN_MASK
+ }
+ return next
+ }
+
+ // Used for validation in tests only
+ fun <R> map(transform: (E) -> R): List<R> {
+ val res = ArrayList<R>(capacity)
+ _state.value.withState { head, tail ->
+ var index = head
+ while (index and mask != tail and mask) {
+ // replace nulls with placeholders on copy
+ val element = array[index and mask].value
+ @Suppress("UNCHECKED_CAST")
+ if (element != null && element !is Placeholder) res.add(transform(element as E))
+ index++
+ }
+ }
+ return res
+ }
+
+ // Used for validation in tests only
+ fun isClosed(): Boolean = _state.value and CLOSED_MASK != 0L
+
+
+ // Instance of this class is placed into array when we have to copy array, but addLast is in progress --
+ // it had already reserved a slot in the array (with null) and have not yet put its value there.
+ // Placeholder keeps the actual index (not masked) to distinguish placeholders on different wraparounds of array
+ // Internal because of inlining
+ internal class Placeholder(@JvmField val index: Int)
+
+ @Suppress("PrivatePropertyName", "MemberVisibilityCanBePrivate")
+ internal companion object {
+ const val INITIAL_CAPACITY = 8
+
+ const val CAPACITY_BITS = 30
+ const val MAX_CAPACITY_MASK = (1 shl CAPACITY_BITS) - 1
+ const val HEAD_SHIFT = 0
+ const val HEAD_MASK = MAX_CAPACITY_MASK.toLong() shl HEAD_SHIFT
+ const val TAIL_SHIFT = HEAD_SHIFT + CAPACITY_BITS
+ const val TAIL_MASK = MAX_CAPACITY_MASK.toLong() shl TAIL_SHIFT
+
+ const val FROZEN_SHIFT = TAIL_SHIFT + CAPACITY_BITS
+ const val FROZEN_MASK = 1L shl FROZEN_SHIFT
+ const val CLOSED_SHIFT = FROZEN_SHIFT + 1
+ const val CLOSED_MASK = 1L shl CLOSED_SHIFT
+
+ const val MIN_ADD_SPIN_CAPACITY = 1024
+
+ @JvmField val REMOVE_FROZEN = Symbol("REMOVE_FROZEN")
+
+ const val ADD_SUCCESS = 0
+ const val ADD_FROZEN = 1
+ const val ADD_CLOSED = 2
+
+ infix fun Long.wo(other: Long) = this and other.inv()
+ fun Long.updateHead(newHead: Int) = (this wo HEAD_MASK) or (newHead.toLong() shl HEAD_SHIFT)
+ fun Long.updateTail(newTail: Int) = (this wo TAIL_MASK) or (newTail.toLong() shl TAIL_SHIFT)
+
+ inline fun <T> Long.withState(block: (head: Int, tail: Int) -> T): T {
+ val head = ((this and HEAD_MASK) shr HEAD_SHIFT).toInt()
+ val tail = ((this and TAIL_MASK) shr TAIL_SHIFT).toInt()
+ return block(head, tail)
+ }
+
+ // FROZEN | CLOSED
+ fun Long.addFailReason(): Int = if (this and CLOSED_MASK != 0L) ADD_CLOSED else ADD_FROZEN
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt
new file mode 100644
index 00000000..d5eb42de
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+
+/** @suppress */
+@InternalCoroutinesApi // Emulating DI for Kotlin object's
+public interface MainDispatcherFactory {
+ val loadPriority: Int // higher priority wins
+
+ /**
+ * Creates the main dispatcher. [allFactories] parameter contains all factories found by service loader.
+ * This method is not guaranteed to be idempotent.
+ */
+ fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher
+
+ /**
+ * Hint used along with error message when the factory failed to create a dispatcher.
+ */
+ fun hintOnError(): String? = null
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt b/kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt
new file mode 100644
index 00000000..1124ff31
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+internal expect inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T>
diff --git a/kotlinx-coroutines-core/common/src/internal/Scopes.kt b/kotlinx-coroutines-core/common/src/internal/Scopes.kt
new file mode 100644
index 00000000..9197ec83
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/Scopes.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * This is a coroutine instance that is created by [coroutineScope] builder.
+ */
+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?
+ final override fun getStackTraceElement(): StackTraceElement? = null
+ final override val isScopedCoroutine: Boolean get() = true
+
+ override val defaultResumeMode: Int get() = MODE_DIRECT
+
+ internal val parent: Job? get() = parentContext[Job]
+
+ @Suppress("UNCHECKED_CAST")
+ override fun afterCompletionInternal(state: Any?, mode: Int) {
+ if (state is CompletedExceptionally) {
+ val exception = if (mode == MODE_IGNORE) state.cause else recoverStackTrace(state.cause, uCont)
+ uCont.resumeUninterceptedWithExceptionMode(exception, mode)
+ } else {
+ uCont.resumeUninterceptedMode(state as T, mode)
+ }
+ }
+}
+
+internal fun AbstractCoroutine<*>.tryRecover(exception: Throwable): Throwable {
+ val cont = (this as? ScopeCoroutine<*>)?.uCont ?: return exception
+ return recoverStackTrace(exception, cont)
+}
+
+internal class ContextScope(context: CoroutineContext) : CoroutineScope {
+ override val coroutineContext: CoroutineContext = context
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/SegmentQueue.kt b/kotlinx-coroutines-core/common/src/internal/SegmentQueue.kt
new file mode 100644
index 00000000..370fcfc5
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/SegmentQueue.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2016-2019 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.*
+
+/**
+ * Essentially, this segment queue is an infinite array of segments, which is represented as
+ * a Michael-Scott queue of them. All segments are instances of [Segment] class and
+ * follow in natural order (see [Segment.id]) in the queue.
+ */
+internal abstract class SegmentQueue<S: Segment<S>>() {
+ private val _head: AtomicRef<S>
+ /**
+ * Returns the first segment in the queue.
+ */
+ protected val head: S get() = _head.value
+
+ private val _tail: AtomicRef<S>
+ /**
+ * Returns the last segment in the queue.
+ */
+ protected val tail: S get() = _tail.value
+
+ init {
+ val initialSegment = newSegment(0)
+ _head = atomic(initialSegment)
+ _tail = atomic(initialSegment)
+ }
+
+ /**
+ * The implementation should create an instance of segment [S] with the specified id
+ * and initial reference to the previous one.
+ */
+ abstract fun newSegment(id: Long, prev: S? = null): S
+
+ /**
+ * Finds a segment with the specified [id] following by next references from the
+ * [startFrom] segment. The typical use-case is reading [tail] or [head], doing some
+ * synchronization, and invoking [getSegment] or [getSegmentAndMoveHead] correspondingly
+ * to find the required segment.
+ */
+ protected fun getSegment(startFrom: S, id: Long): S? {
+ // Go through `next` references and add new segments if needed,
+ // similarly to the `push` in the Michael-Scott queue algorithm.
+ // The only difference is that `CAS failure` means that the
+ // required segment has already been added, so the algorithm just
+ // uses it. This way, only one segment with each id can be in the queue.
+ var cur: S = startFrom
+ while (cur.id < id) {
+ var curNext = cur.next
+ if (curNext == null) {
+ // Add a new segment.
+ val newTail = newSegment(cur.id + 1, cur)
+ curNext = if (cur.casNext(null, newTail)) {
+ if (cur.removed) {
+ cur.remove()
+ }
+ moveTailForward(newTail)
+ newTail
+ } else {
+ cur.next!!
+ }
+ }
+ cur = curNext
+ }
+ if (cur.id != id) return null
+ return cur
+ }
+
+ /**
+ * Invokes [getSegment] and replaces [head] with the result if its [id] is greater.
+ */
+ protected fun getSegmentAndMoveHead(startFrom: S, id: Long): S? {
+ @Suppress("LeakingThis")
+ if (startFrom.id == id) return startFrom
+ val s = getSegment(startFrom, id) ?: return null
+ moveHeadForward(s)
+ return s
+ }
+
+ /**
+ * Updates [head] to the specified segment
+ * if its `id` is greater.
+ */
+ private fun moveHeadForward(new: S) {
+ _head.loop { curHead ->
+ if (curHead.id > new.id) return
+ if (_head.compareAndSet(curHead, new)) {
+ new.prev.value = null
+ return
+ }
+ }
+ }
+
+ /**
+ * Updates [tail] to the specified segment
+ * if its `id` is greater.
+ */
+ private fun moveTailForward(new: S) {
+ _tail.loop { curTail ->
+ if (curTail.id > new.id) return
+ if (_tail.compareAndSet(curTail, new)) return
+ }
+ }
+}
+
+/**
+ * Each segment in [SegmentQueue] has a unique id and is created by [SegmentQueue.newSegment].
+ * Essentially, this is a node in the Michael-Scott queue algorithm, but with
+ * maintaining [prev] pointer for efficient [remove] implementation.
+ */
+internal abstract class Segment<S: Segment<S>>(val id: Long, prev: S?) {
+ // Pointer to the next segment, updates similarly to the Michael-Scott queue algorithm.
+ private val _next = atomic<S?>(null)
+ val next: S? get() = _next.value
+ fun casNext(expected: S?, value: S?): Boolean = _next.compareAndSet(expected, value)
+ // Pointer to the previous segment, updates in [remove] function.
+ val prev = atomic<S?>(null)
+
+ /**
+ * Returns `true` if this segment is logically removed from the queue.
+ * The [remove] function should be called right after it becomes logically removed.
+ */
+ abstract val removed: Boolean
+
+ init {
+ this.prev.value = prev
+ }
+
+ /**
+ * Removes this segment physically from the segment queue. The segment should be
+ * logically removed (so [removed] returns `true`) at the point of invocation.
+ */
+ fun remove() {
+ assert { removed } // The segment should be logically removed at first
+ // Read `next` and `prev` pointers.
+ var next = this._next.value ?: return // tail cannot be removed
+ var prev = prev.value ?: return // head cannot be removed
+ // Link `next` and `prev`.
+ prev.moveNextToRight(next)
+ while (prev.removed) {
+ prev = prev.prev.value ?: break
+ prev.moveNextToRight(next)
+ }
+ next.movePrevToLeft(prev)
+ while (next.removed) {
+ next = next.next ?: break
+ next.movePrevToLeft(prev)
+ }
+ }
+
+ /**
+ * Updates [next] pointer to the specified segment if
+ * the [id] of the specified segment is greater.
+ */
+ private fun moveNextToRight(next: S) {
+ while (true) {
+ val curNext = this._next.value as S
+ if (next.id <= curNext.id) return
+ if (this._next.compareAndSet(curNext, next)) return
+ }
+ }
+
+ /**
+ * Updates [prev] pointer to the specified segment if
+ * the [id] of the specified segment is lower.
+ */
+ private fun movePrevToLeft(prev: S) {
+ while (true) {
+ val curPrev = this.prev.value ?: return
+ if (curPrev.id <= prev.id) return
+ if (this.prev.compareAndSet(curPrev, prev)) return
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
new file mode 100644
index 00000000..8599143e
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+/**
+ * Tries to recover stacktrace for given [exception] and [continuation].
+ * Stacktrace recovery tries to restore [continuation] stack frames using its debug metadata with [CoroutineStackFrame] API
+ * and then reflectively instantiate exception of given type with original exception as a cause and
+ * sets new stacktrace for wrapping exception.
+ * Some frames may be missing due to tail-call elimination.
+ *
+ * Works only on JVM with enabled debug-mode.
+ */
+internal expect fun <E: Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E
+
+/**
+ * Tries to recover stacktrace for given [exception]. Used in non-suspendable points of awaiting.
+ * Stacktrace recovery tries to instantiate exception of given type with original exception as a cause.
+ * Wrapping exception will have proper stacktrace as it's instantiated in the right context.
+ *
+ * Works only on JVM with enabled debug-mode.
+ */
+internal expect fun <E: Throwable> recoverStackTrace(exception: E): E
+
+// Name conflict with recoverStackTrace
+@Suppress("NOTHING_TO_INLINE")
+internal expect suspend inline fun recoverAndThrow(exception: Throwable): Nothing
+
+/**
+ * The opposite of [recoverStackTrace].
+ * It is guaranteed that `unwrap(recoverStackTrace(e)) === e`
+ */
+internal expect fun <E: Throwable> unwrap(exception: E): E
+
+internal expect class StackTraceElement
+
+internal expect interface CoroutineStackFrame {
+ public val callerFrame: CoroutineStackFrame?
+ public fun getStackTraceElement(): StackTraceElement?
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/Symbol.kt b/kotlinx-coroutines-core/common/src/internal/Symbol.kt
new file mode 100644
index 00000000..6f7f673f
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/Symbol.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+/**
+ * A symbol class that is used to define unique constants that are self-explanatory in debugger.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+internal class Symbol(val symbol: String) {
+ 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
new file mode 100644
index 00000000..8bcc8f04
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public expect open class SynchronizedObject() // marker abstract class
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public expect inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt b/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt
new file mode 100644
index 00000000..b7e67ec0
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmName("SystemPropsKt")
+@file:JvmMultifileClass
+
+package kotlinx.coroutines.internal
+
+import kotlin.jvm.*
+
+/**
+ * Gets the system property indicated by the specified [property name][propertyName],
+ * or returns [defaultValue] if there is no property with that key.
+ *
+ * **Note: this function should be used in JVM tests only, other platforms use the default value.**
+ */
+internal fun systemProp(
+ propertyName: String,
+ defaultValue: Boolean
+): Boolean = systemProp(propertyName)?.toBoolean() ?: defaultValue
+
+/**
+ * Gets the system property indicated by the specified [property name][propertyName],
+ * or returns [defaultValue] if there is no property with that key. It also checks that the result
+ * is between [minValue] and [maxValue] (inclusively), throws [IllegalStateException] if it is not.
+ *
+ * **Note: this function should be used in JVM tests only, other platforms use the default value.**
+ */
+internal fun systemProp(
+ propertyName: String,
+ defaultValue: Int,
+ minValue: Int = 1,
+ maxValue: Int = Int.MAX_VALUE
+): Int = systemProp(propertyName, defaultValue.toLong(), minValue.toLong(), maxValue.toLong()).toInt()
+
+/**
+ * Gets the system property indicated by the specified [property name][propertyName],
+ * or returns [defaultValue] if there is no property with that key. It also checks that the result
+ * is between [minValue] and [maxValue] (inclusively), throws [IllegalStateException] if it is not.
+ *
+ * **Note: this function should be used in JVM tests only, other platforms use the default value.**
+ */
+internal fun systemProp(
+ propertyName: String,
+ defaultValue: Long,
+ minValue: Long = 1,
+ maxValue: Long = Long.MAX_VALUE
+): Long {
+ val value = systemProp(propertyName) ?: return defaultValue
+ val parsed = value.toLongOrNull()
+ ?: error("System property '$propertyName' has unrecognized value '$value'")
+ if (parsed !in minValue..maxValue) {
+ error("System property '$propertyName' should be in range $minValue..$maxValue, but is '$parsed'")
+ }
+ return parsed
+}
+
+/**
+ * Gets the system property indicated by the specified [property name][propertyName],
+ * or returns `null` if there is no property with that key.
+ *
+ * **Note: this function should be used in JVM tests only, other platforms use the default value.**
+ */
+internal expect fun systemProp(propertyName: String): String? \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt b/kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt
new file mode 100644
index 00000000..43b5dbe6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+internal expect fun threadContextElements(context: CoroutineContext): Any
diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt b/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt
new file mode 100644
index 00000000..ddf29888
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+@OptionalExpectation
+@UseExperimental(ExperimentalMultiplatform::class)
+internal expect annotation class NativeThreadLocal()
+
+internal expect class CommonThreadLocal<T>() {
+ fun get(): T
+ fun set(value: T)
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt b/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt
new file mode 100644
index 00000000..0ee570d1
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2016-2019 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.*
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public interface ThreadSafeHeapNode {
+ public var heap: ThreadSafeHeap<*>?
+ public var index: Int
+}
+
+/**
+ * Synchronized binary heap.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public open class ThreadSafeHeap<T> : SynchronizedObject() where T: ThreadSafeHeapNode, T: Comparable<T> {
+ private var a: Array<T?>? = null
+
+ private val _size = atomic(0)
+
+ public var size: Int
+ get() = _size.value
+ private set(value) { _size.value = value }
+
+ public val isEmpty: Boolean get() = size == 0
+
+ public fun clear() = synchronized(this) {
+ a?.fill(null)
+ _size.value = 0
+ }
+
+ public fun peek(): T? = synchronized(this) { firstImpl() }
+
+ public fun removeFirstOrNull(): T? = synchronized(this) {
+ if (size > 0) {
+ removeAtImpl(0)
+ } else {
+ null
+ }
+ }
+
+ // @Synchronized // NOTE! NOTE! NOTE! inline fun cannot be @Synchronized
+ public inline fun removeFirstIf(predicate: (T) -> Boolean): T? = synchronized(this) {
+ val first = firstImpl() ?: return null
+ if (predicate(first)) {
+ removeAtImpl(0)
+ } else {
+ null
+ }
+ }
+
+ public fun addLast(node: T) = synchronized(this) { addImpl(node) }
+
+ // @Synchronized // NOTE! NOTE! NOTE! inline fun cannot be @Synchronized
+ // Condition also receives current first node in the heap
+ public inline fun addLastIf(node: T, cond: (T?) -> Boolean): Boolean = synchronized(this) {
+ if (cond(firstImpl())) {
+ addImpl(node)
+ true
+ } else {
+ false
+ }
+ }
+
+ public fun remove(node: T): Boolean = synchronized(this) {
+ return if (node.heap == null) {
+ false
+ } else {
+ val index = node.index
+ assert { index >= 0 }
+ removeAtImpl(index)
+ true
+ }
+ }
+
+ @PublishedApi
+ internal fun firstImpl(): T? = a?.get(0)
+
+ @PublishedApi
+ internal fun removeAtImpl(index: Int): T {
+ assert { size > 0 }
+ val a = this.a!!
+ size--
+ if (index < size) {
+ swap(index, size)
+ val j = (index - 1) / 2
+ if (index > 0 && a[index]!! < a[j]!!) {
+ swap(index, j)
+ siftUpFrom(j)
+ } else {
+ siftDownFrom(index)
+ }
+ }
+ val result = a[size]!!
+ assert { result.heap === this }
+ result.heap = null
+ result.index = -1
+ a[size] = null
+ return result
+ }
+
+ @PublishedApi
+ internal fun addImpl(node: T) {
+ assert { node.heap == null }
+ node.heap = this
+ val a = realloc()
+ val i = size++
+ a[i] = node
+ node.index = i
+ siftUpFrom(i)
+ }
+
+ private tailrec fun siftUpFrom(i: Int) {
+ if (i <= 0) return
+ val a = a!!
+ val j = (i - 1) / 2
+ if (a[j]!! <= a[i]!!) return
+ swap(i, j)
+ siftUpFrom(j)
+ }
+
+ private tailrec fun siftDownFrom(i: Int) {
+ var j = 2 * i + 1
+ if (j >= size) return
+ val a = a!!
+ if (j + 1 < size && a[j + 1]!! < a[j]!!) j++
+ if (a[i]!! <= a[j]!!) return
+ swap(i, j)
+ siftDownFrom(j)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun realloc(): Array<T?> {
+ val a = this.a
+ return when {
+ a == null -> (arrayOfNulls<ThreadSafeHeapNode>(4) as Array<T?>).also { this.a = it }
+ size >= a.size -> a.copyOf(size * 2).also { this.a = it }
+ else -> a
+ }
+ }
+
+ private fun swap(i: Int, j: Int) {
+ val a = a!!
+ val ni = a[j]!!
+ val nj = a[i]!!
+ a[i] = ni
+ a[j] = nj
+ ni.index = i
+ nj.index = j
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt b/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt
new file mode 100644
index 00000000..246ae2c2
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.intrinsics
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+/**
+ * Use this function to start coroutine in a cancellable way, so that it can be cancelled
+ * while waiting to be dispatched.
+ */
+@InternalCoroutinesApi
+public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>) = runSafely(completion) {
+ createCoroutineUnintercepted(completion).intercepted().resumeCancellable(Unit)
+}
+
+/**
+ * Use this function to start coroutine in a cancellable way, so that it can be cancelled
+ * while waiting to be dispatched.
+ */
+internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
+ runSafely(completion) {
+ createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellable(Unit)
+ }
+
+/**
+ * Runs given block and completes completion with its exception if it occurs.
+ * Rationale: [startCoroutineCancellable] is invoked when we are about to run coroutine asynchronously in its own dispatcher.
+ * Thus if dispatcher throws an exception during coroutine start, coroutine never completes, so we should treat dispatcher exception
+ * as its cause and resume completion.
+ */
+private inline fun runSafely(completion: Continuation<*>, block: () -> Unit) {
+ try {
+ block()
+ } catch (e: Throwable) {
+ completion.resumeWith(Result.failure(e))
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt
new file mode 100644
index 00000000..0da91b9b
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.intrinsics
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+/**
+ * Use this function to restart a coroutine directly from inside of [suspendCoroutine],
+ * when the code is already in the context of this coroutine.
+ * It does not use [ContinuationInterceptor] and does not update the context of the current thread.
+ */
+internal fun <T> (suspend () -> T).startCoroutineUnintercepted(completion: Continuation<T>) {
+ startDirect(completion) { actualCompletion ->
+ startCoroutineUninterceptedOrReturn(actualCompletion)
+ }
+}
+
+/**
+ * Use this function to restart a coroutine directly from inside of [suspendCoroutine],
+ * when the code is already in the context of this coroutine.
+ * It does not use [ContinuationInterceptor] and does not update the context of the current thread.
+ */
+internal fun <R, T> (suspend (R) -> T).startCoroutineUnintercepted(receiver: R, completion: Continuation<T>) {
+ startDirect(completion) { actualCompletion ->
+ startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
+ }
+}
+
+/**
+ * Use this function to start a new coroutine in [CoroutineStart.UNDISPATCHED] mode &mdash;
+ * immediately execute the coroutine in the current thread until the next suspension.
+ * It does not use [ContinuationInterceptor], but updates the context of the current thread for the new coroutine.
+ */
+internal fun <T> (suspend () -> T).startCoroutineUndispatched(completion: Continuation<T>) {
+ startDirect(completion) { actualCompletion ->
+ withCoroutineContext(completion.context, null) {
+ startCoroutineUninterceptedOrReturn(actualCompletion)
+ }
+ }
+}
+
+/**
+ * Use this function to start a new coroutine in [CoroutineStart.UNDISPATCHED] mode &mdash;
+ * immediately execute the coroutine in the current thread until the next suspension.
+ * It does not use [ContinuationInterceptor], but updates the context of the current thread for the new coroutine.
+ */
+internal fun <R, T> (suspend (R) -> T).startCoroutineUndispatched(receiver: R, completion: Continuation<T>) {
+ startDirect(completion) { actualCompletion ->
+ withCoroutineContext(completion.context, null) {
+ startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
+ }
+ }
+}
+
+/**
+ * Starts the given [block] immediately in the current stack-frame until the first suspension point.
+ * This method supports debug probes and thus can intercept completion, thus completion is provided
+ * as the parameter of [block].
+ */
+private inline fun <T> startDirect(completion: Continuation<T>, block: (Continuation<T>) -> Any?) {
+ val actualCompletion = probeCoroutineCreated(completion)
+ val value = try {
+ block(actualCompletion)
+ } catch (e: Throwable) {
+ actualCompletion.resumeWithException(e)
+ return
+ }
+ if (value !== COROUTINE_SUSPENDED) {
+ @Suppress("UNCHECKED_CAST")
+ actualCompletion.resume(value as T)
+ }
+}
+
+/**
+ * Starts this coroutine with the given code [block] in the same context and returns the coroutine result when it
+ * completes without suspension.
+ * 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].
+ */
+internal fun <T, R> AbstractCoroutine<T>.startUndispatchedOrReturn(receiver: R, block: suspend R.() -> T): Any? {
+ initParentJob()
+ return undispatchedResult({ true }) {
+ block.startCoroutineUninterceptedOrReturn(receiver, this)
+ }
+}
+
+/**
+ * Same as [startUndispatchedOrReturn], but ignores [TimeoutCancellationException] on fast-path.
+ */
+internal fun <T, R> AbstractCoroutine<T>.startUndispatchedOrReturnIgnoreTimeout(
+ receiver: R, block: suspend R.() -> T): Any? {
+ initParentJob()
+ return undispatchedResult({ e -> !(e is TimeoutCancellationException && e.coroutine === this) }) {
+ block.startCoroutineUninterceptedOrReturn(receiver, this)
+ }
+}
+
+private inline fun <T> AbstractCoroutine<T>.undispatchedResult(
+ shouldThrow: (Throwable) -> Boolean,
+ startBlock: () -> Any?
+): Any? {
+ val result = try {
+ startBlock()
+ } catch (e: Throwable) {
+ CompletedExceptionally(e)
+ }
+
+ /*
+ * We're trying to complete our undispatched block here and have three code-paths:
+ * 1) Suspended.
+ *
+ * Or we are completing our block (and its job).
+ * 2) If we can't complete it, we suspend, probably waiting for children (2)
+ * 3) If we have successfully completed the whole coroutine here in an undispatched manner,
+ * we should decide which result to return. We have two options: either return proposed update or actual final state.
+ * But if fact returning proposed value is not an option, otherwise we will ignore possible cancellation or child failure.
+ *
+ * shouldThrow parameter is a special code path for timeout coroutine:
+ * If timeout is exceeded, but withTimeout() block was not suspended, we would like to return block value,
+ * not a timeout exception.
+ */
+ return when {
+ result === COROUTINE_SUSPENDED -> COROUTINE_SUSPENDED
+ makeCompletingOnce(result, MODE_IGNORE) -> {
+ val state = state
+ if (state is CompletedExceptionally) {
+ when {
+ shouldThrow(state.cause) -> throw tryRecover(state.cause)
+ result is CompletedExceptionally -> throw tryRecover(result.cause)
+ else -> result
+ }
+ } else {
+ state.unboxState()
+ }
+ }
+ else -> COROUTINE_SUSPENDED
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt
new file mode 100644
index 00000000..4626fe1d
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/selects/Select.kt
@@ -0,0 +1,440 @@
+/*
+ * 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 kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.sync.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.jvm.*
+
+/**
+ * Scope for [select] invocation.
+ */
+public interface SelectBuilder<in R> {
+ /**
+ * Registers a clause in this [select] expression without additional parameters that does not select any value.
+ */
+ public operator fun SelectClause0.invoke(block: suspend () -> R)
+
+ /**
+ * Registers clause in this [select] expression without additional parameters that selects value of type [Q].
+ */
+ public operator fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R)
+
+ /**
+ * Registers clause in this [select] expression with additional parameter of type [P] that selects value of type [Q].
+ */
+ public operator fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R)
+
+ /**
+ * Registers clause in this [select] expression with additional parameter nullable parameter of type [P]
+ * with the `null` value for this parameter that selects value of type [Q].
+ */
+ public operator fun <P, Q> SelectClause2<P?, Q>.invoke(block: suspend (Q) -> R) = invoke(null, block)
+
+ /**
+ * Clause that selects the given [block] after a specified timeout passes.
+ * If timeout is negative or zero, [block] is selected immediately.
+ *
+ * **Note: This is an experimental api.** It may be replaced with light-weight timer/timeout channels in the future.
+ *
+ * @param timeMillis timeout time in milliseconds.
+ */
+ @ExperimentalCoroutinesApi
+ public fun onTimeout(timeMillis: Long, block: suspend () -> R)
+}
+
+/**
+ * Clause for [select] expression without additional parameters that does not select any value.
+ */
+public interface SelectClause0 {
+ /**
+ * Registers this clause with the specified [select] instance and [block] of code.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public fun <R> registerSelectClause0(select: SelectInstance<R>, block: suspend () -> R)
+}
+
+/**
+ * Clause for [select] expression without additional parameters that selects value of type [Q].
+ */
+public interface SelectClause1<out Q> {
+ /**
+ * Registers this clause with the specified [select] instance and [block] of code.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (Q) -> R)
+}
+
+/**
+ * Clause for [select] expression with additional parameter of type [P] that selects value of type [Q].
+ */
+public interface SelectClause2<in P, out Q> {
+ /**
+ * Registers this clause with the specified [select] instance and [block] of code.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public fun <R> registerSelectClause2(select: SelectInstance<R>, param: P, block: suspend (Q) -> R)
+}
+
+/**
+ * Internal representation of select instance. This instance is called _selected_ when
+ * the clause to execute is already picked.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+@InternalCoroutinesApi
+public interface SelectInstance<in R> {
+ /**
+ * Returns `true` when this [select] statement had already picked a clause to execute.
+ */
+ public val isSelected: Boolean
+
+ /**
+ * Tries to select this instance.
+ */
+ public fun trySelect(idempotent: Any?): Boolean
+
+ /**
+ * Performs action atomically with [trySelect].
+ */
+ public fun performAtomicTrySelect(desc: AtomicDesc): Any?
+
+ /**
+ * Returns completion continuation of this select instance.
+ * This select instance must be _selected_ first.
+ * All resumption through this instance happen _directly_ without going through dispatcher ([MODE_DIRECT]).
+ */
+ public val completion: Continuation<R>
+
+ /**
+ * Resumes this instance in a cancellable way ([MODE_CANCELLABLE]).
+ */
+ public fun resumeSelectCancellableWithException(exception: Throwable)
+
+ /**
+ * Disposes the specified handle when this instance is selected.
+ * Note, that [DisposableHandle.dispose] could be called multiple times.
+ */
+ public fun disposeOnSelect(handle: DisposableHandle)
+}
+
+/**
+ * Waits for the result of multiple suspending functions simultaneously, which are specified using _clauses_
+ * in the [builder] scope of this select invocation. The caller is suspended until one of the clauses
+ * is either _selected_ or _fails_.
+ *
+ * At most one clause is *atomically* selected and its block is executed. The result of the selected clause
+ * becomes the result of the select. If any clause _fails_, then the select invocation produces the
+ * corresponding exception. No clause is selected in this case.
+ *
+ * This select function is _biased_ to the first clause. When multiple clauses can be selected at the same time,
+ * the first one of them gets priority. Use [selectUnbiased] for an unbiased (randomized) selection among
+ * the clauses.
+
+ * There is no `default` clause for select expression. Instead, each selectable suspending function has the
+ * 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
+ *
+ * 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].
+ *
+ * Atomicity of cancellation depends on the clause: [onSend][SendChannel.onSend], [onReceive][ReceiveChannel.onReceive],
+ * [onReceiveOrNull][ReceiveChannel.onReceiveOrNull], and [onLock][Mutex.onLock] clauses are
+ * *atomically cancellable*. When select throws [CancellationException] it means that those clauses had not performed
+ * their respective operations.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this select operation
+ * was already resumed on atomically cancellable clause and the continuation was posted for execution to the thread's queue.
+ *
+ * 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.
+ */
+public suspend inline fun <R> select(crossinline builder: SelectBuilder<R>.() -> Unit): R =
+ suspendCoroutineUninterceptedOrReturn { uCont ->
+ val scope = SelectBuilderImpl(uCont)
+ try {
+ builder(scope)
+ } catch (e: Throwable) {
+ scope.handleBuilderException(e)
+ }
+ scope.getResult()
+ }
+
+
+@SharedImmutable
+internal val ALREADY_SELECTED: Any = Symbol("ALREADY_SELECTED")
+@SharedImmutable
+private val UNDECIDED: Any = Symbol("UNDECIDED")
+@SharedImmutable
+private val RESUMED: Any = Symbol("RESUMED")
+
+@PublishedApi
+internal class SelectBuilderImpl<in R>(
+ private val uCont: Continuation<R> // unintercepted delegate continuation
+) : LockFreeLinkedListHead(), SelectBuilder<R>,
+ SelectInstance<R>, Continuation<R>, CoroutineStackFrame {
+ override val callerFrame: CoroutineStackFrame?
+ get() = uCont as? CoroutineStackFrame
+
+ override fun getStackTraceElement(): StackTraceElement? = null
+
+ // selection state is "this" (list of nodes) initially and is replaced by idempotent marker (or null) when selected
+ private val _state = atomic<Any?>(this)
+
+ // this is basically our own SafeContinuation
+ private val _result = atomic<Any?>(UNDECIDED)
+
+ // cancellability support
+ @Volatile
+ private var parentHandle: DisposableHandle? = null
+
+ /* Result state machine
+
+ +-----------+ getResult +---------------------+ resume +---------+
+ | UNDECIDED | ------------> | COROUTINE_SUSPENDED | ---------> | RESUMED |
+ +-----------+ +---------------------+ +---------+
+ |
+ | resume
+ V
+ +------------+ getResult
+ | value/Fail | -----------+
+ +------------+ |
+ ^ |
+ | |
+ +-------------------+
+ */
+
+ override val context: CoroutineContext get() = uCont.context
+
+ override val completion: Continuation<R> get() = this
+
+ private inline fun doResume(value: () -> Any?, block: () -> Unit) {
+ assert { isSelected } // "Must be selected first"
+ _result.loop { result ->
+ when {
+ result === UNDECIDED -> if (_result.compareAndSet(UNDECIDED, value())) return
+ result === COROUTINE_SUSPENDED -> if (_result.compareAndSet(COROUTINE_SUSPENDED,
+ RESUMED
+ )) {
+ block()
+ return
+ }
+ else -> throw IllegalStateException("Already resumed")
+ }
+ }
+ }
+
+ // Resumes in MODE_DIRECT
+ override fun resumeWith(result: Result<R>) {
+ doResume({ result.toState() }) {
+ if (result.isFailure) {
+ uCont.resumeWithStackTrace(result.exceptionOrNull()!!)
+ } else {
+ uCont.resumeWith(result)
+ }
+ }
+ }
+
+ // Resumes in MODE_CANCELLABLE
+ override fun resumeSelectCancellableWithException(exception: Throwable) {
+ doResume({ CompletedExceptionally(exception) }) {
+ uCont.intercepted().resumeCancellableWithException(exception)
+ }
+ }
+
+ @PublishedApi
+ internal fun getResult(): Any? {
+ if (!isSelected) initCancellability()
+ var result = _result.value // atomic read
+ if (result === UNDECIDED) {
+ if (_result.compareAndSet(UNDECIDED, COROUTINE_SUSPENDED)) return COROUTINE_SUSPENDED
+ result = _result.value // reread volatile var
+ }
+ when {
+ result === RESUMED -> throw IllegalStateException("Already resumed")
+ result is CompletedExceptionally -> throw result.cause
+ else -> return result // either COROUTINE_SUSPENDED or data
+ }
+ }
+
+ private fun initCancellability() {
+ val parent = context[Job] ?: return
+ val newRegistration = parent.invokeOnCompletion(
+ onCancelling = true, handler = SelectOnCancelling(parent).asHandler)
+ parentHandle = newRegistration
+ // now check our state _after_ registering
+ if (isSelected) newRegistration.dispose()
+ }
+
+ private inner class SelectOnCancelling(job: Job) : JobCancellingNode<Job>(job) {
+ // Note: may be invoked multiple times, but only the first trySelect succeeds anyway
+ override fun invoke(cause: Throwable?) {
+ if (trySelect(null))
+ resumeSelectCancellableWithException(job.getCancellationException())
+ }
+ override fun toString(): String = "SelectOnCancelling[${this@SelectBuilderImpl}]"
+ }
+
+ private val state: Any? get() {
+ _state.loop { state ->
+ if (state !is OpDescriptor) return state
+ state.perform(this)
+ }
+ }
+
+ @PublishedApi
+ internal fun handleBuilderException(e: Throwable) {
+ if (trySelect(null)) {
+ resumeWithException(e)
+ } else if (e !is CancellationException) {
+ /*
+ * Cannot handle this exception -- builder was already resumed with a different exception,
+ * so treat it as "unhandled exception". But only if it is not the completion reason
+ * and it's not the cancellation. Otherwise, in the face of structured concurrency
+ * the same exception will be reported to theglobal exception handler.
+ */
+ val result = getResult()
+ if (result !is CompletedExceptionally || unwrap(result.cause) !== unwrap(e)) {
+ handleCoroutineException(context, e)
+ }
+ }
+ }
+
+ override val isSelected: Boolean get() = state !== this
+
+ override fun disposeOnSelect(handle: DisposableHandle) {
+ val node = DisposeNode(handle)
+ // check-add-check pattern is Ok here since handle.dispose() is safe to be called multiple times
+ if (!isSelected) {
+ addLast(node) // add handle to list
+ // double-check node after adding
+ if (!isSelected) return // all ok - still not selected
+ }
+ // already selected
+ handle.dispose()
+ }
+
+ private fun doAfterSelect() {
+ parentHandle?.dispose()
+ forEach<DisposeNode> {
+ it.handle.dispose()
+ }
+ }
+
+ // it is just like start(), but support idempotent start
+ override fun trySelect(idempotent: Any?): Boolean {
+ assert { idempotent !is OpDescriptor } // "cannot use OpDescriptor as idempotent marker"
+ while (true) { // lock-free loop on state
+ val state = this.state
+ when {
+ state === this -> {
+ if (_state.compareAndSet(this, idempotent)) {
+ doAfterSelect()
+ return true
+ }
+ }
+ // otherwise -- already selected
+ idempotent == null -> return false // already selected
+ state === idempotent -> return true // was selected with this marker
+ else -> return false
+ }
+ }
+ }
+
+ override fun performAtomicTrySelect(desc: AtomicDesc): Any? =
+ AtomicSelectOp(desc).perform(null)
+
+ private inner class AtomicSelectOp(
+ @JvmField val desc: AtomicDesc
+ ) : AtomicOp<Any?>() {
+ override fun prepare(affected: Any?): Any? {
+ // only originator of operation makes preparation move of installing descriptor into this selector's state
+ // helpers should never do it, or risk ruining progress when they come late
+ if (affected == null) {
+ // we are originator (affected reference is not null if helping)
+ prepareIfNotSelected()?.let { return it }
+ }
+ return desc.prepare(this)
+ }
+
+ override fun complete(affected: Any?, failure: Any?) {
+ completeSelect(failure)
+ desc.complete(this, failure)
+ }
+
+ fun prepareIfNotSelected(): Any? {
+ _state.loop { state ->
+ when {
+ state === this@AtomicSelectOp -> return null // already in progress
+ state is OpDescriptor -> state.perform(this@SelectBuilderImpl) // help
+ state === this@SelectBuilderImpl -> {
+ if (_state.compareAndSet(this@SelectBuilderImpl, this@AtomicSelectOp))
+ return null // success
+ }
+ else -> return ALREADY_SELECTED
+ }
+ }
+ }
+
+ private fun completeSelect(failure: Any?) {
+ val selectSuccess = failure == null
+ val update = if (selectSuccess) null else this@SelectBuilderImpl
+ if (_state.compareAndSet(this@AtomicSelectOp, update)) {
+ if (selectSuccess)
+ doAfterSelect()
+ }
+ }
+ }
+
+ override fun SelectClause0.invoke(block: suspend () -> R) {
+ registerSelectClause0(this@SelectBuilderImpl, block)
+ }
+
+ override fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) {
+ registerSelectClause1(this@SelectBuilderImpl, block)
+ }
+
+ override fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) {
+ registerSelectClause2(this@SelectBuilderImpl, param, block)
+ }
+
+ override fun onTimeout(timeMillis: Long, block: suspend () -> R) {
+ if (timeMillis <= 0L) {
+ if (trySelect(null))
+ block.startCoroutineUnintercepted(completion)
+ return
+ }
+ val action = Runnable {
+ // todo: we could have replaced startCoroutine with startCoroutineUndispatched
+ // But we need a way to know that Delay.invokeOnTimeout had used the right thread
+ if (trySelect(null))
+ block.startCoroutineCancellable(completion) // shall be cancellable while waits for dispatch
+ }
+ disposeOnSelect(context.delay.invokeOnTimeout(timeMillis, action))
+ }
+
+ private class DisposeNode(
+ @JvmField val handle: DisposableHandle
+ ) : LockFreeLinkedListNode()
+}
diff --git a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt
new file mode 100644
index 00000000..37521d8b
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+/**
+ * Waits for the result of multiple suspending functions simultaneously like [select], but in an _unbiased_
+ * way when multiple clauses are selectable at the same time.
+ *
+ * This unbiased implementation of `select` expression randomly shuffles the clauses before checking
+ * if they are selectable, thus ensuring that there is no statistical bias to the selection of the first
+ * clauses.
+ *
+ * See [select] function description for all the other details.
+ */
+public suspend inline fun <R> selectUnbiased(crossinline builder: SelectBuilder<R>.() -> Unit): R =
+ suspendCoroutineUninterceptedOrReturn { uCont ->
+ val scope = UnbiasedSelectBuilderImpl(uCont)
+ try {
+ builder(scope)
+ } catch (e: Throwable) {
+ scope.handleBuilderException(e)
+ }
+ scope.initSelectResult()
+ }
+
+
+@PublishedApi
+internal class UnbiasedSelectBuilderImpl<in R>(uCont: Continuation<R>) :
+ SelectBuilder<R> {
+ val instance = SelectBuilderImpl(uCont)
+ val clauses = arrayListOf<() -> Unit>()
+
+ @PublishedApi
+ internal fun handleBuilderException(e: Throwable) = instance.handleBuilderException(e)
+
+ @PublishedApi
+ internal fun initSelectResult(): Any? {
+ if (!instance.isSelected) {
+ try {
+ clauses.shuffle()
+ clauses.forEach { it.invoke() }
+ } catch (e: Throwable) {
+ instance.handleBuilderException(e)
+ }
+ }
+ return instance.getResult()
+ }
+
+ override fun SelectClause0.invoke(block: suspend () -> R) {
+ clauses += { registerSelectClause0(instance, block) }
+ }
+
+ override fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) {
+ clauses += { registerSelectClause1(instance, block) }
+ }
+
+ override fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) {
+ clauses += { registerSelectClause2(instance, param, block) }
+ }
+
+ override fun onTimeout(timeMillis: Long, block: suspend () -> R) {
+ clauses += { instance.onTimeout(timeMillis, block) }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt b/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt
new file mode 100644
index 00000000..1726f5f0
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+
+/**
+ * Loops while [select] expression returns `true`.
+ *
+ * The statement of the form:
+ *
+ * ```
+ * whileSelect {
+ * /*body*/
+ * }
+ * ```
+ *
+ * is a shortcut for:
+ *
+ * ```
+ * while(select<Boolean> {
+ * /*body*/
+ * }) {}
+ *
+ * **Note: This is an experimental api.** It may be replaced with a higher-performance DSL for selection from loops.
+ */
+@ExperimentalCoroutinesApi
+public suspend inline fun whileSelect(crossinline builder: SelectBuilder<Boolean>.() -> Unit) {
+ while(select<Boolean>(builder)) {}
+}
diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
new file mode 100644
index 00000000..3c729153
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.sync
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Mutual exclusion for coroutines.
+ *
+ * Mutex has two states: _locked_ and _unlocked_.
+ * It is **non-reentrant**, that is invoking [lock] even from the same thread/coroutine that currently holds
+ * the lock still suspends the invoker.
+ *
+ * JVM API note:
+ * Memory semantic of the [Mutex] is similar to `synchronized` block on JVM:
+ * An unlock on a [Mutex] happens-before every subsequent successful lock on that [Mutex].
+ * Unsuccessful call to [tryLock] do not have any memory effects.
+ */
+public interface Mutex {
+ /**
+ * Returns `true` when this mutex is locked.
+ */
+ public val isLocked: Boolean
+
+ /**
+ * Tries to lock this mutex, returning `false` if this mutex is already locked.
+ *
+ * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex
+ * is already locked with the same token (same identity), this function throws [IllegalStateException].
+ */
+ public fun tryLock(owner: Any? = null): Boolean
+
+ /**
+ * Locks this mutex, suspending caller while the mutex is locked.
+ *
+ * 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].
+ *
+ * *Cancellation of suspended lock invocation is atomic* -- when this function
+ * throws [CancellationException] it means that the mutex was not locked.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this lock operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * 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] invocation with [onLock] clause.
+ * Use [tryLock] to try acquire lock without waiting.
+ *
+ * This function is fair; suspended callers are resumed in first-in-first-out order.
+ *
+ * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex
+ * is already locked with the same token (same identity), this function throws [IllegalStateException].
+ */
+ public suspend fun lock(owner: Any? = null)
+
+ /**
+ * Clause for [select] expression of [lock] suspending function that selects when the mutex is locked.
+ * Additional parameter for the clause in the `owner` (see [lock]) and when the clause is selected
+ * the reference to this mutex is passed into the corresponding block.
+ */
+ public val onLock: SelectClause2<Any?, Mutex>
+
+ /**
+ * Checks mutex locked by owner
+ *
+ * @return `true` on mutex lock by owner, `false` if not locker or it is locked by different owner
+ */
+ public fun holdsLock(owner: Any): Boolean
+
+ /**
+ * Unlocks this mutex. Throws [IllegalStateException] if invoked on a mutex that is not locked or
+ * was locked with a different owner token (by identity).
+ *
+ * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex
+ * was locked with the different token (by identity), this function throws [IllegalStateException].
+ */
+ public fun unlock(owner: Any? = null)
+}
+
+/**
+ * Creates a [Mutex] instance.
+ * The mutex created is fair: lock is granted in first come, first served order.
+ *
+ * @param locked initial state of the mutex.
+ */
+@Suppress("FunctionName")
+public fun Mutex(locked: Boolean = false): Mutex =
+ MutexImpl(locked)
+
+/**
+ * Executes the given [action] under this mutex's lock.
+ *
+ * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex
+ * is already locked with the same token (same identity), this function throws [IllegalStateException].
+ *
+ * @return the return value of the action.
+ */
+public suspend inline fun <T> Mutex.withLock(owner: Any? = null, action: () -> T): T {
+ lock(owner)
+ try {
+ return action()
+ } finally {
+ unlock(owner)
+ }
+}
+
+@SharedImmutable
+private val LOCK_FAIL = Symbol("LOCK_FAIL")
+@SharedImmutable
+private val ENQUEUE_FAIL = Symbol("ENQUEUE_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")
+
+@SharedImmutable
+private val EMPTY_LOCKED = Empty(LOCKED)
+@SharedImmutable
+private val EMPTY_UNLOCKED = Empty(UNLOCKED)
+
+private class Empty(
+ @JvmField val locked: Any
+) {
+ override fun toString(): String = "Empty[$locked]"
+}
+
+internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
+ // State is: Empty | LockedQueue | OpDescriptor
+ // shared objects while we have no waiters
+ private val _state = atomic<Any?>(if (locked) EMPTY_LOCKED else EMPTY_UNLOCKED)
+
+ public override val isLocked: Boolean get() {
+ _state.loop { state ->
+ when (state) {
+ is Empty -> return state.locked !== UNLOCKED
+ is LockedQueue -> return true
+ is OpDescriptor -> state.perform(this) // help
+ else -> error("Illegal state $state")
+ }
+ }
+ }
+
+ // for tests ONLY
+ internal val isLockedEmptyQueueState: Boolean get() {
+ val state = _state.value
+ return state is LockedQueue && state.isEmpty
+ }
+
+ public override fun tryLock(owner: Any?): Boolean {
+ _state.loop { state ->
+ when (state) {
+ is Empty -> {
+ if (state.locked !== UNLOCKED) return false
+ val update = if (owner == null) EMPTY_LOCKED else Empty(
+ owner
+ )
+ if (_state.compareAndSet(state, update)) return true
+ }
+ is LockedQueue -> {
+ check(state.owner !== owner) { "Already locked by $owner" }
+ return false
+ }
+ is OpDescriptor -> state.perform(this) // help
+ else -> error("Illegal state $state")
+ }
+ }
+ }
+
+ public override suspend fun lock(owner: Any?) {
+ // fast-path -- try lock
+ if (tryLock(owner)) return
+ // slow-path -- suspend
+ return lockSuspend(owner)
+ }
+
+ private suspend fun lockSuspend(owner: Any?) = suspendAtomicCancellableCoroutine<Unit> sc@ { cont ->
+ val waiter = LockCont(owner, cont)
+ _state.loop { state ->
+ when (state) {
+ is Empty -> {
+ if (state.locked !== UNLOCKED) { // try upgrade to queue & retry
+ _state.compareAndSet(state, LockedQueue(state.locked))
+ } else {
+ // try lock
+ val update = if (owner == null) EMPTY_LOCKED else Empty(owner)
+ if (_state.compareAndSet(state, update)) { // locked
+ cont.resume(Unit)
+ return@sc
+ }
+ }
+ }
+ is LockedQueue -> {
+ val curOwner = state.owner
+ check(curOwner !== owner) { "Already locked by $owner" }
+ if (state.addLastIf(waiter) { _state.value === state }) {
+ // added to waiter list!
+ cont.removeOnCancellation(waiter)
+ return@sc
+ }
+ }
+ is OpDescriptor -> state.perform(this) // help
+ else -> error("Illegal state $state")
+ }
+ }
+ }
+
+ override val onLock: SelectClause2<Any?, Mutex>
+ get() = this
+
+ // registerSelectLock
+ @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, owner: Any?, block: suspend (Mutex) -> R) {
+ while (true) { // lock-free loop on state
+ if (select.isSelected) return
+ when (val state = _state.value) {
+ is Empty -> {
+ if (state.locked !== UNLOCKED) { // try upgrade to queue & retry
+ _state.compareAndSet(state, LockedQueue(state.locked))
+ } else {
+ // try lock
+ val failure = select.performAtomicTrySelect(TryLockDesc(this, owner))
+ when {
+ failure == null -> { // success
+ block.startCoroutineUnintercepted(receiver = this, completion = select.completion)
+ return
+ }
+ failure === ALREADY_SELECTED -> return // already selected -- bail out
+ failure === LOCK_FAIL -> {} // retry
+ else -> error("performAtomicTrySelect(TryLockDesc) returned $failure")
+ }
+ }
+ }
+ is LockedQueue -> {
+ check(state.owner !== owner) { "Already locked by $owner" }
+ val node = LockSelect(owner, this, select, block)
+ if (state.addLastIf(node) { _state.value === state }) {
+ // successfully enqueued
+ select.disposeOnSelect(node)
+ return
+ }
+ }
+ is OpDescriptor -> state.perform(this) // help
+ else -> error("Illegal state $state")
+ }
+ }
+ }
+
+ private class TryLockDesc(
+ @JvmField val mutex: MutexImpl,
+ @JvmField val owner: Any?
+ ) : AtomicDesc() {
+ // This is Harris's RDCSS (Restricted Double-Compare Single Swap) operation
+ private inner class PrepareOp(private val op: AtomicOp<*>) : OpDescriptor() {
+ override fun perform(affected: Any?): Any? {
+ val update: Any = if (op.isDecided) EMPTY_UNLOCKED else op // restore if was already decided
+ (affected as MutexImpl)._state.compareAndSet(this, update)
+ return null // ok
+ }
+ }
+
+ override fun prepare(op: AtomicOp<*>): Any? {
+ val prepare = PrepareOp(op)
+ if (!mutex._state.compareAndSet(EMPTY_UNLOCKED, prepare)) return LOCK_FAIL
+ return prepare.perform(mutex)
+ }
+
+ override fun complete(op: AtomicOp<*>, failure: Any?) {
+ val update = if (failure != null) EMPTY_UNLOCKED else {
+ if (owner == null) EMPTY_LOCKED else Empty(owner)
+ }
+ mutex._state.compareAndSet(op, update)
+ }
+ }
+
+ public override fun holdsLock(owner: Any) =
+ _state.value.let { state ->
+ when (state) {
+ is Empty -> state.locked === owner
+ is LockedQueue -> state.owner === owner
+ else -> false
+ }
+ }
+
+ public override fun unlock(owner: Any?) {
+ _state.loop { state ->
+ when (state) {
+ is Empty -> {
+ if (owner == null)
+ check(state.locked !== UNLOCKED) { "Mutex is not locked" }
+ else
+ check(state.locked === owner) { "Mutex is locked by ${state.locked} but expected $owner" }
+ if (_state.compareAndSet(state, EMPTY_UNLOCKED)) return
+ }
+ is OpDescriptor -> state.perform(this)
+ is LockedQueue -> {
+ if (owner != null)
+ check(state.owner === owner) { "Mutex is locked by ${state.owner} but expected $owner" }
+ val waiter = state.removeFirstOrNull()
+ if (waiter == null) {
+ val op = UnlockOp(state)
+ if (_state.compareAndSet(state, op) && op.perform(this) == null) return
+ } else {
+ val token = (waiter as LockWaiter).tryResumeLockWaiter()
+ if (token != null) {
+ state.owner = waiter.owner ?: LOCKED
+ waiter.completeResumeLockWaiter(token)
+ return
+ }
+ }
+ }
+ else -> error("Illegal state $state")
+ }
+ }
+ }
+
+ override fun toString(): String {
+ _state.loop { state ->
+ when (state) {
+ is Empty -> return "Mutex[${state.locked}]"
+ is OpDescriptor -> state.perform(this)
+ is LockedQueue -> return "Mutex[${state.owner}]"
+ else -> error("Illegal state $state")
+ }
+ }
+ }
+
+ private class LockedQueue(
+ @JvmField var owner: Any
+ ) : LockFreeLinkedListHead() {
+ override fun toString(): String = "LockedQueue[$owner]"
+ }
+
+ private abstract class LockWaiter(
+ @JvmField val owner: Any?
+ ) : LockFreeLinkedListNode(), DisposableHandle {
+ final override fun dispose() { remove() }
+ abstract fun tryResumeLockWaiter(): Any?
+ abstract fun completeResumeLockWaiter(token: Any)
+ }
+
+ private class LockCont(
+ owner: Any?,
+ @JvmField val cont: CancellableContinuation<Unit>
+ ) : LockWaiter(owner) {
+ override fun tryResumeLockWaiter() = cont.tryResume(Unit)
+ override fun completeResumeLockWaiter(token: Any) = cont.completeResume(token)
+ override fun toString(): String = "LockCont[$owner, $cont]"
+ }
+
+ private class LockSelect<R>(
+ owner: Any?,
+ @JvmField val mutex: Mutex,
+ @JvmField val select: SelectInstance<R>,
+ @JvmField val block: suspend (Mutex) -> R
+ ) : LockWaiter(owner) {
+ override fun tryResumeLockWaiter(): Any? = if (select.trySelect(null)) SELECT_SUCCESS else null
+ override fun completeResumeLockWaiter(token: Any) {
+ assert { token === SELECT_SUCCESS }
+ block.startCoroutine(receiver = mutex, completion = select.completion)
+ }
+ override fun toString(): String = "LockSelect[$owner, $mutex, $select]"
+ }
+
+ // atomic unlock operation that checks that waiters queue is empty
+ private class UnlockOp(
+ @JvmField val queue: LockedQueue
+ ) : OpDescriptor() {
+ override fun perform(affected: Any?): Any? {
+ /*
+ Note: queue cannot change while this UnlockOp is in progress, so all concurrent attempts to
+ make a decision will reach it consistently. It does not matter what is a proposed
+ decision when this UnlockOp is no longer active, because in this case the following CAS
+ will fail anyway.
+ */
+ val success = queue.isEmpty
+ val update: Any = if (success) EMPTY_UNLOCKED else queue
+ (affected as MutexImpl)._state.compareAndSet(this@UnlockOp, update)
+ /*
+ `perform` invocation from the original `unlock` invocation may be coming too late, when
+ some other thread had already helped to complete it (either successfully or not).
+ That operation was unsuccessful if `state` was restored to this `queue` reference and
+ that is what is being checked below.
+ */
+ return if (affected._state.value === queue) UNLOCK_FAIL else null
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
new file mode 100644
index 00000000..a9df15cf
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
@@ -0,0 +1,211 @@
+package kotlinx.coroutines.sync
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+import kotlin.math.*
+
+/**
+ * A counting semaphore for coroutines that logically maintains a number of available permits.
+ * Each [acquire] takes a single permit or suspends until it is available.
+ * Each [release] adds a permit, potentially releasing a suspended acquirer.
+ * Semaphore is fair and maintains a FIFO order of acquirers.
+ *
+ * Semaphores are mostly used to limit the number of coroutines that have an access to particular resource.
+ * Semaphore with `permits = 1` is essentially a [Mutex].
+ **/
+public interface Semaphore {
+ /**
+ * Returns the current number of permits available in this semaphore.
+ */
+ public val availablePermits: Int
+
+ /**
+ * Acquires a permit from this semaphore, suspending until one is available.
+ * All suspending acquirers are processed in first-in-first-out (FIFO) order.
+ *
+ * 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].
+ *
+ * *Cancellation of suspended semaphore acquisition is atomic* -- when this function
+ * throws [CancellationException] it means that the semaphore was not acquired.
+ *
+ * Note, that this function does not check for cancellation when it does not suspend.
+ * Use [CoroutineScope.isActive] or [CoroutineScope.ensureActive] to periodically
+ * check for cancellation in tight loops if needed.
+ *
+ * Use [tryAcquire] to try acquire a permit of this semaphore without suspension.
+ */
+ public suspend fun acquire()
+
+ /**
+ * Tries to acquire a permit from this semaphore without suspension.
+ *
+ * @return `true` if a permit was acquired, `false` otherwise.
+ */
+ public fun tryAcquire(): Boolean
+
+ /**
+ * Releases a permit, returning it into this semaphore. Resumes the first
+ * suspending acquirer if there is one at the point of invocation.
+ * Throws [IllegalStateException] if the number of [release] invocations is greater than the number of preceding [acquire].
+ */
+ public fun release()
+}
+
+/**
+ * Creates new [Semaphore] instance.
+ * @param permits the number of permits available in this semaphore.
+ * @param acquiredPermits the number of already acquired permits,
+ * should be between `0` and `permits` (inclusively).
+ */
+@Suppress("FunctionName")
+public fun Semaphore(permits: Int, acquiredPermits: Int = 0): Semaphore = SemaphoreImpl(permits, acquiredPermits)
+
+/**
+ * Executes the given [action], acquiring a permit from this semaphore at the beginning
+ * and releasing it after the [action] is completed.
+ *
+ * @return the return value of the [action].
+ */
+public suspend inline fun <T> Semaphore.withPermit(action: () -> T): T {
+ acquire()
+ try {
+ return action()
+ } finally {
+ release()
+ }
+}
+
+private class SemaphoreImpl(
+ private val permits: Int, acquiredPermits: Int
+) : Semaphore, SegmentQueue<SemaphoreSegment>() {
+ init {
+ require(permits > 0) { "Semaphore should have at least 1 permit, but had $permits" }
+ require(acquiredPermits in 0..permits) { "The number of acquired permits should be in 0..$permits" }
+ }
+
+ override fun newSegment(id: Long, prev: SemaphoreSegment?) = SemaphoreSegment(id, prev)
+
+ /**
+ * This counter indicates a number of available permits if it is non-negative,
+ * or the size with minus sign otherwise. Note, that 32-bit counter is enough here
+ * since the maximal number of available permits is [permits] which is [Int],
+ * and the maximum number of waiting acquirers cannot be greater than 2^31 in any
+ * real application.
+ */
+ private val _availablePermits = atomic(permits - acquiredPermits)
+ override val availablePermits: Int get() = max(_availablePermits.value, 0)
+
+ // The queue of waiting acquirers is essentially an infinite array based on `SegmentQueue`;
+ // each segment contains a fixed number of slots. To determine a slot for each enqueue
+ // and dequeue operation, we increment the corresponding counter at the beginning of the operation
+ // and use the value before the increment as a slot number. This way, each enqueue-dequeue pair
+ // works with an individual cell.
+ private val enqIdx = atomic(0L)
+ private val deqIdx = atomic(0L)
+
+ override fun tryAcquire(): Boolean {
+ _availablePermits.loop { p ->
+ if (p <= 0) return false
+ if (_availablePermits.compareAndSet(p, p - 1)) return true
+ }
+ }
+
+ override suspend fun acquire() {
+ val p = _availablePermits.getAndDecrement()
+ if (p > 0) return // permit acquired
+ addToQueueAndSuspend()
+ }
+
+ override fun release() {
+ val p = incPermits()
+ if (p >= 0) return // no waiters
+ resumeNextFromQueue()
+ }
+
+ fun incPermits() = _availablePermits.getAndUpdate { cur ->
+ check(cur < permits) { "The number of released permits cannot be greater than $permits" }
+ cur + 1
+ }
+
+ private suspend fun addToQueueAndSuspend() = suspendAtomicCancellableCoroutine<Unit> sc@ { cont ->
+ val last = this.tail
+ val enqIdx = enqIdx.getAndIncrement()
+ val segment = getSegment(last, enqIdx / SEGMENT_SIZE)
+ val i = (enqIdx % SEGMENT_SIZE).toInt()
+ if (segment === null || segment.get(i) === RESUMED || !segment.cas(i, null, cont)) {
+ // already resumed
+ cont.resume(Unit)
+ return@sc
+ }
+ cont.invokeOnCancellation(CancelSemaphoreAcquisitionHandler(this, segment, i).asHandler)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ internal fun resumeNextFromQueue() {
+ try_again@while (true) {
+ val first = this.head
+ val deqIdx = deqIdx.getAndIncrement()
+ val segment = getSegmentAndMoveHead(first, deqIdx / SEGMENT_SIZE) ?: continue@try_again
+ val i = (deqIdx % SEGMENT_SIZE).toInt()
+ val cont = segment.getAndSet(i, RESUMED)
+ if (cont === null) return // just resumed
+ if (cont === CANCELLED) continue@try_again
+ (cont as CancellableContinuation<Unit>).resume(Unit)
+ return
+ }
+ }
+}
+
+private class CancelSemaphoreAcquisitionHandler(
+ private val semaphore: SemaphoreImpl,
+ private val segment: SemaphoreSegment,
+ private val index: Int
+) : CancelHandler() {
+ override fun invoke(cause: Throwable?) {
+ val p = semaphore.incPermits()
+ if (p >= 0) return
+ if (segment.cancel(index)) return
+ semaphore.resumeNextFromQueue()
+ }
+
+ override fun toString() = "CancelSemaphoreAcquisitionHandler[$semaphore, $segment, $index]"
+}
+
+private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?): Segment<SemaphoreSegment>(id, prev) {
+ val acquirers = atomicArrayOfNulls<Any?>(SEGMENT_SIZE)
+ private val cancelledSlots = atomic(0)
+ override val removed get() = cancelledSlots.value == SEGMENT_SIZE
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun get(index: Int): Any? = acquirers[index].value
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun cas(index: Int, expected: Any?, value: Any?): Boolean = acquirers[index].compareAndSet(expected, value)
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun getAndSet(index: Int, value: Any?) = acquirers[index].getAndSet(value)
+
+ // Cleans the acquirer slot located by the specified index
+ // and removes this segment physically if all slots are cleaned.
+ fun cancel(index: Int): Boolean {
+ // Try to cancel the slot
+ val cancelled = getAndSet(index, CANCELLED) !== RESUMED
+ // Remove this segment if needed
+ if (cancelledSlots.incrementAndGet() == SEGMENT_SIZE)
+ remove()
+ return cancelled
+ }
+
+ override fun toString() = "SemaphoreSegment[id=$id, hashCode=${hashCode()}]"
+}
+
+@SharedImmutable
+private val RESUMED = Symbol("RESUMED")
+@SharedImmutable
+private val CANCELLED = Symbol("CANCELLED")
+@SharedImmutable
+private val SEGMENT_SIZE = systemProp("kotlinx.coroutines.semaphore.segmentSize", 16) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt b/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt
new file mode 100644
index 00000000..ffde0f96
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.test.*
+
+@Suppress("DEPRECATION") // cancel(cause)
+class AbstractCoroutineTest : TestBase() {
+ @Test
+ fun testNotifications() = runTest {
+ expect(1)
+ val coroutineContext = coroutineContext // workaround for KT-22984
+ val coroutine = object : AbstractCoroutine<String>(coroutineContext, false) {
+ override fun onStart() {
+ expect(3)
+ }
+
+ override fun onCancelling(cause: Throwable?) {
+ assertEquals(null, cause)
+ expect(5)
+ }
+
+ override fun onCompleted(value: String) {
+ assertEquals("OK", value)
+ expect(6)
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ expectUnreached()
+ }
+ }
+
+ coroutine.invokeOnCompletion(onCancelling = true) {
+ assertEquals(null, it)
+ expect(7)
+ }
+
+ coroutine.invokeOnCompletion {
+ assertEquals(null, it)
+ expect(8)
+ }
+ expect(2)
+ coroutine.start()
+ expect(4)
+ coroutine.resume("OK")
+ finish(9)
+ }
+
+ @Test
+ fun testNotificationsWithException() = runTest {
+ expect(1)
+ val coroutineContext = coroutineContext // workaround for KT-22984
+ val coroutine = object : AbstractCoroutine<String>(coroutineContext + NonCancellable, false) {
+ override fun onStart() {
+ expect(3)
+ }
+
+ override fun onCancelling(cause: Throwable?) {
+ assertTrue(cause is TestException1)
+ expect(5)
+ }
+
+ override fun onCompleted(value: String) {
+ expectUnreached()
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ assertTrue(cause is TestException1)
+ expect(8)
+ }
+ }
+
+ coroutine.invokeOnCompletion(onCancelling = true) {
+ assertTrue(it is TestException1)
+ expect(6)
+ }
+
+ coroutine.invokeOnCompletion {
+ assertTrue(it is TestException1)
+ expect(9)
+ }
+
+ expect(2)
+ coroutine.start()
+ expect(4)
+ coroutine.cancelCoroutine(TestException1())
+ expect(7)
+ 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
new file mode 100644
index 00000000..5b23b641
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class AsyncLazyTest : TestBase() {
+
+ @Test
+ fun testSimple() = runTest {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expect(3)
+ 42
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted)
+ assertEquals(d.await(), 42)
+ assertTrue(!d.isActive && d.isCompleted && !d.isCancelled)
+ expect(4)
+ assertEquals(d.await(), 42) // second await -- same result
+ finish(5)
+ }
+
+ @Test
+ fun testLazyDeferAndYield() = runTest {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expect(3)
+ yield() // this has not effect, because parent coroutine is waiting
+ expect(4)
+ 42
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted)
+ assertEquals(d.await(), 42)
+ assertTrue(!d.isActive && d.isCompleted && !d.isCancelled)
+ expect(5)
+ assertEquals(d.await(), 42) // second await -- same result
+ finish(6)
+ }
+
+ @Test
+ fun testLazyDeferAndYield2() = runTest {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expect(7)
+ 42
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted)
+ launch { // see how it looks from another coroutine
+ expect(4)
+ assertTrue(!d.isActive && !d.isCompleted)
+ yield() // yield back to main
+ expect(6)
+ assertTrue(d.isActive && !d.isCompleted) // implicitly started by main's await
+ yield() // yield to d
+ }
+ expect(3)
+ assertTrue(!d.isActive && !d.isCompleted)
+ yield() // yield to second child (lazy async is not computing yet)
+ expect(5)
+ assertTrue(!d.isActive && !d.isCompleted)
+ assertEquals(d.await(), 42) // starts computing
+ assertTrue(!d.isActive && d.isCompleted && !d.isCancelled)
+ finish(8)
+ }
+
+ @Test
+ fun testSimpleException() = runTest(
+ expected = { it is TestException }
+ ) {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ finish(3)
+ throw TestException()
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted)
+ d.await() // will throw IOException
+ }
+
+ @Test
+ fun testLazyDeferAndYieldException() = runTest(
+ expected = { it is TestException }
+ ) {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expect(3)
+ yield() // this has not effect, because parent coroutine is waiting
+ finish(4)
+ throw TestException()
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted)
+ d.await() // will throw IOException
+ }
+
+ @Test
+ fun testCatchException() = runTest {
+ expect(1)
+ val d = async(NonCancellable, start = CoroutineStart.LAZY) {
+ expect(3)
+ throw TestException()
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted)
+ try {
+ d.await() // will throw IOException
+ } catch (e: TestException) {
+ assertTrue(!d.isActive && d.isCompleted && d.isCancelled)
+ expect(4)
+ }
+ finish(5)
+ }
+
+ @Test
+ fun testStart() = runTest {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expect(4)
+ 42
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted)
+ assertTrue(d.start())
+ assertTrue(d.isActive && !d.isCompleted)
+ expect(3)
+ assertTrue(!d.start())
+ yield() // yield to started coroutine
+ assertTrue(!d.isActive && d.isCompleted && !d.isCancelled) // and it finishes
+ expect(5)
+ assertEquals(d.await(), 42) // await sees result
+ finish(6)
+ }
+
+ @Test
+ fun testCancelBeforeStart() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expectUnreached()
+ 42
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted)
+ d.cancel()
+ assertTrue(!d.isActive && d.isCompleted && d.isCancelled)
+ assertTrue(!d.start())
+ finish(3)
+ assertEquals(d.await(), 42) // await shall throw CancellationException
+ expectUnreached()
+ }
+
+ @Test
+ fun testCancelWhileComputing() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expect(4)
+ yield() // yield to main, that is going to cancel us
+ expectUnreached()
+ 42
+ }
+ expect(2)
+ assertTrue(!d.isActive && !d.isCompleted && !d.isCancelled)
+ assertTrue(d.start())
+ assertTrue(d.isActive && !d.isCompleted && !d.isCancelled)
+ expect(3)
+ yield() // yield to d
+ expect(5)
+ assertTrue(d.isActive && !d.isCompleted && !d.isCancelled)
+ d.cancel()
+ assertTrue(!d.isActive && d.isCancelled) // cancelling !
+ assertTrue(!d.isActive && d.isCancelled) // still cancelling
+ finish(6)
+ 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
new file mode 100644
index 00000000..6fd4ebbe
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/AsyncTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED", "UNREACHABLE_CODE", "USELESS_IS_CHECK") // KT-21913
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+@Suppress("DEPRECATION") // cancel(cause)
+class AsyncTest : TestBase() {
+
+ @Test
+ fun testSimple() = runTest {
+ expect(1)
+ val d = async {
+ expect(3)
+ 42
+ }
+ expect(2)
+ assertTrue(d.isActive)
+ assertEquals(d.await(), 42)
+ assertTrue(!d.isActive)
+ expect(4)
+ assertEquals(d.await(), 42) // second await -- same result
+ finish(5)
+ }
+
+ @Test
+ fun testUndispatched() = runTest {
+ expect(1)
+ val d = async(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ 42
+ }
+ expect(3)
+ assertTrue(!d.isActive)
+ assertEquals(d.await(), 42)
+ finish(4)
+ }
+
+ @Test
+ fun testSimpleException() = runTest(expected = { it is TestException }) {
+ expect(1)
+ val d = async {
+ finish(3)
+ throw TestException()
+ }
+ expect(2)
+ d.await() // will throw TestException
+ }
+
+ @Test
+ fun testCancellationWithCause() = runTest {
+ expect(1)
+ val d = async(NonCancellable, start = CoroutineStart.ATOMIC) {
+ expect(3)
+ yield()
+ }
+ expect(2)
+ d.cancel(TestCancellationException("TEST"))
+ try {
+ d.await()
+ } catch (e: TestCancellationException) {
+ finish(4)
+ assertEquals("TEST", e.message)
+ }
+ }
+
+ @Test
+ fun testLostException() = runTest {
+ expect(1)
+ val deferred = async(Job()) {
+ expect(2)
+ throw Exception()
+ }
+
+ // Exception is not consumed -> nothing is reported
+ deferred.join()
+ finish(3)
+ }
+
+ @Test
+ fun testParallelDecompositionCaughtException() = runTest {
+ val deferred = async(NonCancellable) {
+ val decomposed = async(NonCancellable) {
+ throw TestException()
+ 1
+ }
+ try {
+ decomposed.await()
+ } catch (e: TestException) {
+ 42
+ }
+ }
+ assertEquals(42, deferred.await())
+ }
+
+ @Test
+ fun testParallelDecompositionCaughtExceptionWithInheritedParent() = runTest {
+ expect(1)
+ val deferred = async(NonCancellable) {
+ expect(2)
+ val decomposed = async { // inherits parent job!
+ expect(3)
+ throw TestException()
+ 1
+ }
+ try {
+ decomposed.await()
+ } catch (e: TestException) {
+ expect(4) // Should catch this exception, but parent is already cancelled
+ 42
+ }
+ }
+ try {
+ // This will fail
+ assertEquals(42, deferred.await())
+ } catch (e: TestException) {
+ finish(5)
+ }
+ }
+
+ @Test
+ fun testParallelDecompositionUncaughtExceptionWithInheritedParent() = runTest(expected = { it is TestException }) {
+ val deferred = async(NonCancellable) {
+ val decomposed = async {
+ throw TestException()
+ 1
+ }
+
+ decomposed.await()
+ }
+
+ deferred.await()
+ expectUnreached()
+ }
+
+ @Test
+ fun testParallelDecompositionUncaughtException() = runTest(expected = { it is TestException }) {
+ val deferred = async(NonCancellable) {
+ val decomposed = async {
+ throw TestException()
+ 1
+ }
+
+ decomposed.await()
+ }
+
+ deferred.await()
+ expectUnreached()
+ }
+
+ @Test
+ fun testCancellationTransparency() = runTest {
+ val deferred = async(NonCancellable, start = CoroutineStart.ATOMIC) {
+ expect(2)
+ throw TestException()
+ }
+ expect(1)
+ deferred.cancel()
+ try {
+ deferred.await()
+ } catch (e: TestException) {
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testDeferAndYieldException() = runTest(expected = { it is TestException }) {
+ expect(1)
+ val d = async {
+ expect(3)
+ yield() // no effect, parent waiting
+ finish(4)
+ throw TestException()
+ }
+ expect(2)
+ d.await() // will throw IOException
+ }
+
+ @Test
+ fun testDeferWithTwoWaiters() = runTest {
+ expect(1)
+ val d = async {
+ expect(5)
+ yield()
+ expect(9)
+ 42
+ }
+ expect(2)
+ launch {
+ expect(6)
+ assertEquals(d.await(), 42)
+ expect(11)
+ }
+ expect(3)
+ launch {
+ expect(7)
+ assertEquals(d.await(), 42)
+ expect(12)
+ }
+ expect(4)
+ yield() // this actually yields control to async, which produces results and resumes both waiters (in order)
+ expect(8)
+ yield() // yield again to "d", which completes
+ expect(10)
+ yield() // yield to both waiters
+ finish(13)
+ }
+
+ class BadClass {
+ override fun equals(other: Any?): Boolean = error("equals")
+ override fun hashCode(): Int = error("hashCode")
+ override fun toString(): String = error("toString")
+ }
+
+ @Test
+ fun testDeferBadClass() = runTest {
+ val bad = BadClass()
+ val d = async {
+ expect(1)
+ bad
+ }
+ assertSame(d.await(), bad)
+ finish(2)
+ }
+
+ @Test
+ fun testOverriddenParent() = runTest {
+ val parent = Job()
+ val deferred = async(parent, CoroutineStart.ATOMIC) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ parent.cancel()
+ try {
+ expect(1)
+ deferred.await()
+ } catch (e: CancellationException) {
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testIncompleteAsyncState() = runTest {
+ val deferred = async {
+ coroutineContext[Job]!!.invokeOnCompletion { }
+ }
+
+ deferred.await().dispose()
+ assertTrue(deferred.getCompleted() is DisposableHandle)
+ assertNull(deferred.getCompletionExceptionOrNull())
+ assertTrue(deferred.isCompleted)
+ assertFalse(deferred.isActive)
+ assertFalse(deferred.isCancelled)
+ }
+
+ @Test
+ fun testIncompleteAsyncFastPath() = runTest {
+ val deferred = async(Dispatchers.Unconfined) {
+ coroutineContext[Job]!!.invokeOnCompletion { }
+ }
+
+ deferred.await().dispose()
+ assertTrue(deferred.getCompleted() is DisposableHandle)
+ assertNull(deferred.getCompletionExceptionOrNull())
+ assertTrue(deferred.isCompleted)
+ assertFalse(deferred.isActive)
+ assertFalse(deferred.isCancelled)
+ }
+
+}
diff --git a/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt
new file mode 100644
index 00000000..a9f58dd6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.selects.*
+import kotlinx.coroutines.sync.*
+import kotlin.test.*
+
+class AtomicCancellationCommonTest : TestBase() {
+ @Test
+ fun testCancellableLaunch() = runTest {
+ expect(1)
+ val job = launch {
+ expectUnreached() // will get cancelled before start
+ }
+ expect(2)
+ job.cancel()
+ finish(3)
+ }
+
+ @Test
+ fun testAtomicLaunch() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.ATOMIC) {
+ finish(4) // will execute even after it was cancelled
+ }
+ expect(2)
+ job.cancel()
+ expect(3)
+ }
+
+ @Test
+ fun testDeferredAwaitCancellable() = runTest {
+ expect(1)
+ val deferred = async { // deferred, not yet complete
+ expect(4)
+ "OK"
+ }
+ assertEquals(false, deferred.isCompleted)
+ var job: Job? = null
+ launch { // will cancel job as soon as deferred completes
+ expect(5)
+ assertEquals(true, deferred.isCompleted)
+ job!!.cancel()
+ }
+ job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ deferred.await() // suspends
+ expectUnreached() // will not execute -- cancelled while dispatched
+ } finally {
+ finish(7) // but will execute finally blocks
+ }
+ }
+ expect(3) // continues to execute when the job suspends
+ yield() // to deferred & canceller
+ expect(6)
+ }
+
+ @Test
+ fun testJobJoinCancellable() = runTest {
+ expect(1)
+ val jobToJoin = launch { // not yet complete
+ expect(4)
+ }
+ assertEquals(false, jobToJoin.isCompleted)
+ var job: Job? = null
+ launch { // will cancel job as soon as jobToJoin completes
+ expect(5)
+ assertEquals(true, jobToJoin.isCompleted)
+ job!!.cancel()
+ }
+ job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ jobToJoin.join() // suspends
+ expectUnreached() // will not execute -- cancelled while dispatched
+ } finally {
+ finish(7) // but will execute finally blocks
+ }
+ }
+ expect(3) // continues to execute when the job suspends
+ yield() // to jobToJoin & canceller
+ expect(6)
+ }
+
+ @Test
+ fun testLockAtomicCancel() = runTest {
+ expect(1)
+ val mutex = Mutex(true) // locked mutex
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ mutex.lock() // suspends
+ expect(4) // should execute despite cancellation
+ }
+ expect(3)
+ mutex.unlock() // unlock mutex first
+ job.cancel() // cancel the job next
+ yield() // now yield
+ finish(5)
+ }
+
+ @Test
+ fun testSelectLockAtomicCancel() = runTest {
+ expect(1)
+ val mutex = Mutex(true) // locked mutex
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ val result = select<String> { // suspends
+ mutex.onLock {
+ expect(4)
+ "OK"
+ }
+ }
+ assertEquals("OK", result)
+ expect(5) // should execute despite cancellation
+ }
+ expect(3)
+ mutex.unlock() // unlock mutex first
+ job.cancel() // cancel the job next
+ yield() // now yield
+ finish(6)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/AwaitTest.kt b/kotlinx-coroutines-core/common/test/AwaitTest.kt
new file mode 100644
index 00000000..0949b62c
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/AwaitTest.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class AwaitTest : TestBase() {
+
+ @Test
+ fun testAwaitAll() = runTest {
+ expect(1)
+ val d = async {
+ expect(3)
+ "OK"
+ }
+
+ val d2 = async {
+ yield()
+ expect(4)
+ 1L
+ }
+
+ expect(2)
+ require(d2.isActive && !d2.isCompleted)
+
+ assertEquals(listOf("OK", 1L), awaitAll(d, d2))
+ expect(5)
+
+ require(d.isCompleted && d2.isCompleted)
+ require(!d.isCancelled && !d2.isCancelled)
+ finish(6)
+ }
+
+ @Test
+ fun testAwaitAllLazy() = runTest {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expect(2)
+ 1
+ }
+ val d2 = async(start = CoroutineStart.LAZY) {
+ expect(3)
+ 2
+ }
+ assertEquals(listOf(1, 2), awaitAll(d, d2))
+ finish(4)
+ }
+
+ @Test
+ fun testAwaitAllTyped() = runTest {
+ val d1 = async { 1L }
+ val d2 = async { "" }
+ val d3 = async { }
+
+ assertEquals(listOf(1L, ""), listOf(d1, d2).awaitAll())
+ assertEquals(listOf(1L, Unit), listOf(d1, d3).awaitAll())
+ assertEquals(listOf("", Unit), listOf(d2, d3).awaitAll())
+ }
+
+ @Test
+ fun testAwaitAllExceptionally() = runTest {
+ expect(1)
+ val d = async {
+ expect(3)
+ "OK"
+ }
+
+ val d2 = async(NonCancellable) {
+ yield()
+ throw TestException()
+ }
+
+ val d3 = async {
+ expect(4)
+ delay(Long.MAX_VALUE)
+ 1
+ }
+
+ expect(2)
+ try {
+ awaitAll(d, d2, d3)
+ } catch (e: TestException) {
+ expect(5)
+ }
+
+ yield()
+ require(d.isCompleted && d2.isCancelled && d3.isActive)
+ d3.cancel()
+ finish(6)
+ }
+
+ @Test
+ fun testAwaitAllMultipleExceptions() = runTest {
+ val d = async(NonCancellable) {
+ expect(2)
+ throw TestException()
+ }
+
+ val d2 = async(NonCancellable) {
+ yield()
+ throw TestException()
+ }
+
+ val d3 = async {
+ yield()
+ }
+
+ expect(1)
+ try {
+ awaitAll(d, d2, d3)
+ } catch (e: TestException) {
+ expect(3)
+ }
+
+ finish(4)
+ }
+
+ @Test
+ fun testAwaitAllCancellation() = runTest {
+ val outer = async {
+
+ expect(1)
+ val inner = async {
+ expect(4)
+ delay(Long.MAX_VALUE)
+ }
+
+ expect(2)
+ awaitAll(inner)
+ expectUnreached()
+ }
+
+ yield()
+ expect(3)
+ yield()
+ require(outer.isActive)
+ outer.cancel()
+ require(outer.isCancelled)
+ finish(5)
+ }
+
+ @Test
+ fun testAwaitAllPartiallyCompleted() = runTest {
+ val d1 = async { expect(1); 1 }
+ d1.await()
+ val d2 = async { expect(3); 2 }
+ expect(2)
+ assertEquals(listOf(1, 2), awaitAll(d1, d2))
+ require(d1.isCompleted && d2.isCompleted)
+ finish(4)
+ }
+
+ @Test
+ fun testAwaitAllPartiallyCompletedExceptionally() = runTest {
+ val d1 = async(NonCancellable) {
+ expect(1)
+ throw TestException()
+ }
+
+ yield()
+
+ // This job is called after exception propagation
+ val d2 = async { expect(4) }
+
+ expect(2)
+ try {
+ awaitAll(d1, d2)
+ expectUnreached()
+ } catch (e: TestException) {
+ expect(3)
+ }
+
+ require(d2.isActive)
+ d2.await()
+ require(d1.isCompleted && d2.isCompleted)
+ finish(5)
+ }
+
+ @Test
+ fun testAwaitAllFullyCompleted() = runTest {
+ val d1 = CompletableDeferred(Unit)
+ val d2 = CompletableDeferred(Unit)
+ val job = async { expect(3) }
+ expect(1)
+ awaitAll(d1, d2)
+ expect(2)
+ job.await()
+ finish(4)
+ }
+
+ @Test
+ fun testAwaitOnSet() = runTest {
+ val d1 = CompletableDeferred(Unit)
+ val d2 = CompletableDeferred(Unit)
+ val job = async { expect(2) }
+ expect(1)
+ listOf(d1, d2, job).awaitAll()
+ finish(3)
+ }
+
+ @Test
+ fun testAwaitAllFullyCompletedExceptionally() = runTest {
+ val d1 = CompletableDeferred<Unit>(parent = null)
+ .apply { completeExceptionally(TestException()) }
+ val d2 = CompletableDeferred<Unit>(parent = null)
+ .apply { completeExceptionally(TestException()) }
+ val job = async { expect(3) }
+ expect(1)
+ try {
+ awaitAll(d1, d2)
+ } catch (e: TestException) {
+ expect(2)
+ }
+
+ job.await()
+ finish(4)
+ }
+
+ @Test
+ fun testAwaitAllSameJobMultipleTimes() = runTest {
+ val d = async { "OK" }
+ // Duplicates are allowed though kdoc doesn't guarantee that
+ assertEquals(listOf("OK", "OK", "OK"), awaitAll(d, d, d))
+ }
+
+ @Test
+ fun testAwaitAllSameThrowingJobMultipleTimes() = runTest {
+ val d1 =
+ async(NonCancellable) { throw TestException() }
+ val d2 = async { } // do nothing
+
+ try {
+ expect(1)
+ // Duplicates are allowed though kdoc doesn't guarantee that
+ awaitAll(d1, d2, d1, d2)
+ expectUnreached()
+ } catch (e: TestException) {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testAwaitAllEmpty() = runTest {
+ expect(1)
+ assertEquals(emptyList(), awaitAll<Unit>())
+ assertEquals(emptyList(), emptyList<Deferred<Unit>>().awaitAll())
+ finish(2)
+ }
+
+ // joinAll
+
+ @Test
+ fun testJoinAll() = runTest {
+ val d1 = launch { expect(2) }
+ val d2 = async {
+ expect(3)
+ "OK"
+ }
+ val d3 = launch { expect(4) }
+
+ expect(1)
+ joinAll(d1, d2, d3)
+ finish(5)
+ }
+
+ @Test
+ fun testJoinAllLazy() = runTest {
+ expect(1)
+ val d = async(start = CoroutineStart.LAZY) {
+ expect(2)
+ }
+ val d2 = launch(start = CoroutineStart.LAZY) {
+ expect(3)
+ }
+ joinAll(d, d2)
+ finish(4)
+ }
+
+ @Test
+ fun testJoinAllExceptionally() = runTest {
+ val d1 = launch {
+ expect(2)
+ }
+ val d2 = async(NonCancellable) {
+ expect(3)
+ throw TestException()
+ }
+ val d3 = async {
+ expect(4)
+ }
+
+ expect(1)
+ joinAll(d1, d2, d3)
+ finish(5)
+ }
+
+ @Test
+ fun testJoinAllCancellation() = runTest {
+ val outer = launch {
+ expect(2)
+ val inner = launch {
+ expect(3)
+ delay(Long.MAX_VALUE)
+ }
+
+ joinAll(inner)
+ expectUnreached()
+ }
+
+ expect(1)
+ yield()
+ require(outer.isActive)
+ yield()
+ outer.cancel()
+ outer.join()
+ finish(4)
+ }
+
+ @Test
+ fun testJoinAllAlreadyCompleted() = runTest {
+ val job = launch {
+ expect(1)
+ }
+
+ job.join()
+ expect(2)
+
+ joinAll(job)
+ finish(3)
+ }
+
+ @Test
+ fun testJoinAllEmpty() = runTest {
+ expect(1)
+ joinAll()
+ listOf<Job>().joinAll()
+ finish(2)
+ }
+
+ @Test
+ fun testJoinAllSameJob() = runTest {
+ val job = launch { }
+ joinAll(job, job, job)
+ }
+
+ @Test
+ fun testJoinAllSameJobExceptionally() = runTest {
+ val job =
+ async(NonCancellable) { throw TestException() }
+ joinAll(job, job, job)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt b/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt
new file mode 100644
index 00000000..00f719e6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class CancellableContinuationHandlersTest : TestBase() {
+
+ @Test
+ fun testDoubleSubscription() = runTest({ it is IllegalStateException }) {
+ suspendCancellableCoroutine<Unit> { c ->
+ c.invokeOnCancellation { finish(1) }
+ c.invokeOnCancellation { expectUnreached() }
+ }
+ }
+
+ @Test
+ fun testDoubleSubscriptionAfterCompletion() = runTest {
+ suspendCancellableCoroutine<Unit> { c ->
+ c.resume(Unit)
+ // Nothing happened
+ c.invokeOnCancellation { expectUnreached() }
+ // Cannot validate after completion
+ c.invokeOnCancellation { expectUnreached() }
+ }
+ }
+
+ @Test
+ fun testDoubleSubscriptionAfterCancellation() = runTest {
+ try {
+ suspendCancellableCoroutine<Unit> { c ->
+ c.cancel()
+ c.invokeOnCancellation {
+ assertTrue(it is CancellationException)
+ expect(1)
+ }
+ assertFailsWith<IllegalStateException> { c.invokeOnCancellation { expectUnreached() } }
+ }
+ } catch (e: CancellationException) {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testDoubleSubscriptionAfterCancellationWithCause() = runTest {
+ try {
+ suspendCancellableCoroutine<Unit> { c ->
+ c.cancel(AssertionError())
+ c.invokeOnCancellation {
+ require(it is AssertionError)
+ expect(1)
+ }
+ assertFailsWith<IllegalStateException> { c.invokeOnCancellation { expectUnreached() } }
+ }
+ } catch (e: AssertionError) {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testDoubleSubscriptionMixed() = runTest {
+ try {
+ suspendCancellableCoroutine<Unit> { c ->
+ c.invokeOnCancellation {
+ require(it is IndexOutOfBoundsException)
+ expect(1)
+ }
+ c.cancel(IndexOutOfBoundsException())
+ assertFailsWith<IllegalStateException> { c.invokeOnCancellation { expectUnreached() } }
+ }
+ } catch (e: IndexOutOfBoundsException) {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testExceptionInHandler() = runTest(
+ unhandled = listOf({ it -> it is CompletionHandlerException })
+ ) {
+ expect(1)
+ try {
+ suspendCancellableCoroutine<Unit> { c ->
+ c.invokeOnCancellation { throw AssertionError() }
+ c.cancel()
+ }
+ } catch (e: CancellationException) {
+ expect(2)
+ }
+ finish(3)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/CancellableContinuationTest.kt b/kotlinx-coroutines-core/common/test/CancellableContinuationTest.kt
new file mode 100644
index 00000000..38fc9ff2
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CancellableContinuationTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class CancellableContinuationTest : TestBase() {
+ @Test
+ fun testResumeWithExceptionAndResumeWithException() = runTest {
+ var continuation: Continuation<Unit>? = null
+ val job = launch {
+ try {
+ expect(2)
+ suspendCancellableCoroutine<Unit> { c ->
+ continuation = c
+ }
+ } catch (e: TestException) {
+ expect(3)
+ }
+ }
+ expect(1)
+ yield()
+ continuation!!.resumeWithException(TestException())
+ yield()
+ assertFailsWith<IllegalStateException> { continuation!!.resumeWithException(TestException()) }
+ job.join()
+ finish(4)
+ }
+
+ @Test
+ fun testResumeAndResumeWithException() = runTest {
+ var continuation: Continuation<Unit>? = null
+ val job = launch {
+ expect(2)
+ suspendCancellableCoroutine<Unit> { c ->
+ continuation = c
+ }
+ expect(3)
+ }
+ expect(1)
+ yield()
+ continuation!!.resume(Unit)
+ job.join()
+ assertFailsWith<IllegalStateException> { continuation!!.resumeWithException(TestException()) }
+ finish(4)
+ }
+
+ @Test
+ fun testResumeAndResume() = runTest {
+ var continuation: Continuation<Unit>? = null
+ val job = launch {
+ expect(2)
+ suspendCancellableCoroutine<Unit> { c ->
+ continuation = c
+ }
+ expect(3)
+ }
+ expect(1)
+ yield()
+ continuation!!.resume(Unit)
+ job.join()
+ assertFailsWith<IllegalStateException> { continuation!!.resume(Unit) }
+ finish(4)
+ }
+
+ /**
+ * Cancelling outer job may, in practise, race with attempt to resume continuation and resumes
+ * should be ignored. Here suspended coroutine is cancelled but then resumed with exception.
+ */
+ @Test
+ fun testCancelAndResumeWithException() = runTest {
+ var continuation: Continuation<Unit>? = null
+ val job = launch {
+ try {
+ expect(2)
+ suspendCancellableCoroutine<Unit> { c ->
+ continuation = c
+ }
+ } catch (e: CancellationException) {
+ expect(3)
+ }
+ }
+ expect(1)
+ yield()
+ job.cancel() // Cancel job
+ yield()
+ continuation!!.resumeWithException(TestException()) // Should not fail
+ finish(4)
+ }
+
+ /**
+ * Cancelling outer job may, in practise, race with attempt to resume continuation and resumes
+ * should be ignored. Here suspended coroutine is cancelled but then resumed with exception.
+ */
+ @Test
+ fun testCancelAndResume() = runTest {
+ var continuation: Continuation<Unit>? = null
+ val job = launch {
+ try {
+ expect(2)
+ suspendCancellableCoroutine<Unit> { c ->
+ continuation = c
+ }
+ } catch (e: CancellationException) {
+ expect(3)
+ }
+ }
+ expect(1)
+ yield()
+ job.cancel() // Cancel job
+ yield()
+ continuation!!.resume(Unit) // Should not fail
+ finish(4)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt b/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt
new file mode 100644
index 00000000..b2cde6b9
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016-2019 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
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+/**
+ * Test for [CancellableContinuation.resume] with `onCancellation` parameter.
+ */
+class CancellableResumeTest : TestBase() {
+ @Test
+ fun testResumeImmediateNormally() = runTest {
+ expect(1)
+ val ok = suspendCancellableCoroutine<String> { cont ->
+ expect(2)
+ cont.invokeOnCancellation { expectUnreached() }
+ cont.resume("OK") { expectUnreached() }
+ expect(3)
+ }
+ assertEquals("OK", ok)
+ finish(4)
+ }
+
+ @Test
+ fun testResumeImmediateAfterCancel() = runTest(
+ expected = { it is TestException }
+ ) {
+ expect(1)
+ val ok = suspendCancellableCoroutine<String> { cont ->
+ expect(2)
+ cont.invokeOnCancellation { expect(3) }
+ cont.cancel(TestException("FAIL"))
+ expect(4)
+ cont.resume("OK") { cause ->
+ expect(5)
+ assertTrue(cause is TestException)
+ }
+ finish(6)
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testResumeLaterNormally() = runTest {
+ expect(1)
+ lateinit var cc: CancellableContinuation<String>
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ val ok = suspendCancellableCoroutine<String> { cont ->
+ expect(3)
+ cont.invokeOnCancellation { expectUnreached() }
+ cc = cont
+ }
+ assertEquals("OK", ok)
+ finish(6)
+ }
+ expect(4)
+ cc.resume("OK") { expectUnreached() }
+ expect(5)
+ }
+
+ @Test
+ fun testResumeLaterAfterCancel() = runTest {
+ expect(1)
+ lateinit var cc: CancellableContinuation<String>
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ suspendCancellableCoroutine<String> { cont ->
+ expect(3)
+ cont.invokeOnCancellation { expect(5) }
+ cc = cont
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ finish(9)
+ }
+ }
+ expect(4)
+ job.cancel(TestCancellationException())
+ expect(6)
+ cc.resume("OK") { cause ->
+ expect(7)
+ assertTrue(cause is TestCancellationException)
+ }
+ expect(8)
+ }
+
+ @Test
+ fun testResumeCancelWhileDispatched() = runTest {
+ expect(1)
+ lateinit var cc: CancellableContinuation<String>
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ suspendCancellableCoroutine<String> { cont ->
+ expect(3)
+ // resumed first, then cancelled, so no invokeOnCancellation call
+ cont.invokeOnCancellation { expectUnreached() }
+ cc = cont
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ expect(8)
+ }
+ }
+ expect(4)
+ cc.resume("OK") { cause ->
+ expect(7)
+ assertTrue(cause is TestCancellationException)
+ }
+ expect(5)
+ job.cancel(TestCancellationException()) // cancel while execution is dispatched
+ expect(6)
+ yield() // to coroutine -- throws cancellation exception
+ finish(9)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/CompletableDeferredTest.kt b/kotlinx-coroutines-core/common/test/CompletableDeferredTest.kt
new file mode 100644
index 00000000..999ff862
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CompletableDeferredTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED", "DEPRECATION") // KT-21913
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class CompletableDeferredTest : TestBase() {
+ @Test
+ fun testFresh() {
+ val c = CompletableDeferred<String>()
+ checkFresh(c)
+ }
+
+ @Test
+ fun testComplete() {
+ val c = CompletableDeferred<String>()
+ assertEquals(true, c.complete("OK"))
+ checkCompleteOk(c)
+ assertEquals("OK", c.getCompleted())
+ assertEquals(false, c.complete("OK"))
+ checkCompleteOk(c)
+ assertEquals("OK", c.getCompleted())
+ }
+
+ @Test
+ fun testCompleteWithIncompleteResult() {
+ val c = CompletableDeferred<DisposableHandle>()
+ assertEquals(true, c.complete(c.invokeOnCompletion { }))
+ checkCompleteOk(c)
+ assertEquals(false, c.complete(c.invokeOnCompletion { }))
+ checkCompleteOk(c)
+ assertTrue(c.getCompleted() is Incomplete)
+ }
+
+ private fun checkFresh(c: CompletableDeferred<*>) {
+ assertEquals(true, c.isActive)
+ assertEquals(false, c.isCancelled)
+ assertEquals(false, c.isCompleted)
+ assertThrows<IllegalStateException> { c.getCancellationException() }
+ assertThrows<IllegalStateException> { c.getCompleted() }
+ assertThrows<IllegalStateException> { c.getCompletionExceptionOrNull() }
+ }
+
+ private fun checkCompleteOk(c: CompletableDeferred<*>) {
+ assertEquals(false, c.isActive)
+ assertEquals(false, c.isCancelled)
+ assertEquals(true, c.isCompleted)
+ assertTrue(c.getCancellationException() is JobCancellationException)
+ assertEquals(null, c.getCompletionExceptionOrNull())
+ }
+
+ private fun checkCancel(c: CompletableDeferred<String>) {
+ assertEquals(false, c.isActive)
+ assertEquals(true, c.isCancelled)
+ assertEquals(true, c.isCompleted)
+ assertThrows<CancellationException> { c.getCompleted() }
+ assertTrue(c.getCompletionExceptionOrNull() is CancellationException)
+ }
+
+ @Test
+ fun testCancelWithException() {
+ val c = CompletableDeferred<String>()
+ assertEquals(true, c.completeExceptionally(TestException()))
+ checkCancelWithException(c)
+ assertEquals(false, c.completeExceptionally(TestException()))
+ checkCancelWithException(c)
+ }
+
+ private fun checkCancelWithException(c: CompletableDeferred<String>) {
+ assertEquals(false, c.isActive)
+ assertEquals(true, c.isCancelled)
+ assertEquals(true, c.isCompleted)
+ assertTrue(c.getCancellationException() is JobCancellationException)
+ assertThrows<TestException> { c.getCompleted() }
+ assertTrue(c.getCompletionExceptionOrNull() is TestException)
+ }
+
+ @Test
+ fun testParentCancelsChild() {
+ val parent = Job()
+ val c = CompletableDeferred<String>(parent)
+ checkFresh(c)
+ parent.cancel()
+ assertEquals(false, parent.isActive)
+ assertEquals(true, parent.isCancelled)
+ assertEquals(false, c.isActive)
+ assertEquals(true, c.isCancelled)
+ assertEquals(true, c.isCompleted)
+ assertThrows<CancellationException> { c.getCompleted() }
+ assertTrue(c.getCompletionExceptionOrNull() is CancellationException)
+ }
+
+ @Test
+ fun testParentActiveOnChildCompletion() {
+ val parent = Job()
+ val c = CompletableDeferred<String>(parent)
+ checkFresh(c)
+ assertEquals(true, parent.isActive)
+ assertEquals(true, c.complete("OK"))
+ checkCompleteOk(c)
+ assertEquals(true, parent.isActive)
+ }
+
+ @Test
+ fun testParentCancelledOnChildException() {
+ val parent = Job()
+ val c = CompletableDeferred<String>(parent)
+ checkFresh(c)
+ assertEquals(true, parent.isActive)
+ assertEquals(true, c.completeExceptionally(TestException()))
+ checkCancelWithException(c)
+ assertEquals(false, parent.isActive)
+ assertEquals(true, parent.isCancelled)
+ }
+
+ @Test
+ fun testParentActiveOnChildCancellation() {
+ val parent = Job()
+ val c = CompletableDeferred<String>(parent)
+ checkFresh(c)
+ assertEquals(true, parent.isActive)
+ c.cancel()
+ checkCancel(c)
+ assertEquals(true, parent.isActive)
+ }
+
+ @Test
+ fun testAwait() = runTest {
+ expect(1)
+ val c = CompletableDeferred<String>()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ assertEquals("OK", c.await()) // suspends
+ expect(5)
+ assertEquals("OK", c.await()) // does not suspend
+ expect(6)
+ }
+ expect(3)
+ c.complete("OK")
+ expect(4)
+ yield() // to launch
+ finish(7)
+ }
+
+ @Test
+ fun testCancelAndAwaitParentWaitChildren() = runTest {
+ expect(1)
+ val parent = CompletableDeferred<String>()
+ launch(parent, start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ yield() // will get cancelled
+ } finally {
+ expect(5)
+ }
+ }
+ expect(3)
+ parent.cancel()
+ expect(4)
+ try {
+ parent.await()
+ } catch (e: CancellationException) {
+ finish(6)
+ }
+ }
+
+ @Test
+ fun testCompleteAndAwaitParentWaitChildren() = runTest {
+ expect(1)
+ val parent = CompletableDeferred<String>()
+ launch(parent, start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ yield() // will get cancelled
+ } finally {
+ expect(5)
+ }
+ }
+ expect(3)
+ parent.complete("OK")
+ expect(4)
+ assertEquals("OK", parent.await())
+ finish(6)
+ }
+
+ private inline fun <reified T: Throwable> assertThrows(block: () -> Unit) {
+ try {
+ block()
+ fail("Should not complete normally")
+ } catch (e: Throwable) {
+ assertTrue(e is T)
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/CompletableJobTest.kt b/kotlinx-coroutines-core/common/test/CompletableJobTest.kt
new file mode 100644
index 00000000..335e5d56
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CompletableJobTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class CompletableJobTest {
+ @Test
+ fun testComplete() {
+ val job = Job()
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertTrue(job.complete())
+ assertTrue(job.isCompleted)
+ assertFalse(job.isActive)
+ assertFalse(job.isCancelled)
+ assertFalse(job.complete())
+ }
+
+ @Test
+ fun testCompleteWithException() {
+ val job = Job()
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertTrue(job.completeExceptionally(TestException()))
+ assertTrue(job.isCompleted)
+ assertFalse(job.isActive)
+ assertTrue(job.isCancelled)
+ assertFalse(job.completeExceptionally(TestException()))
+ assertFalse(job.complete())
+ }
+
+ @Test
+ fun testCompleteWithChildren() {
+ val parent = Job()
+ val child = Job(parent)
+ assertTrue(parent.complete())
+ assertFalse(parent.complete())
+ assertTrue(parent.isActive)
+ assertFalse(parent.isCompleted)
+ assertTrue(child.complete())
+ assertTrue(child.isCompleted)
+ assertTrue(parent.isCompleted)
+ assertFalse(child.isActive)
+ assertFalse(parent.isActive)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt b/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt
new file mode 100644
index 00000000..6fdd3bbe
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
+import kotlin.test.*
+
+class CoroutineDispatcherOperatorFunInvokeTest : TestBase() {
+
+ /**
+ * Copy pasted from [WithContextTest.testThrowException],
+ * then edited to use operator.
+ */
+ @Test
+ fun testThrowException() = runTest {
+ expect(1)
+ try {
+ (wrappedCurrentDispatcher()) {
+ expect(2)
+ throw AssertionError()
+ }
+ } catch (e: AssertionError) {
+ expect(3)
+ }
+
+ yield()
+ finish(4)
+ }
+
+ /**
+ * Copy pasted from [WithContextTest.testWithContextChildWaitSameContext],
+ * then edited to use operator fun invoke for [CoroutineDispatcher].
+ */
+ @Test
+ fun testWithContextChildWaitSameContext() = runTest {
+ expect(1)
+ (wrappedCurrentDispatcher()) {
+ expect(2)
+ launch {
+ // ^^^ schedules to main thread
+ expect(4) // waits before return
+ }
+ expect(3)
+ "OK".wrap()
+ }.unwrap()
+ finish(5)
+ }
+
+ private class Wrapper(val value: String) : Incomplete {
+ override val isActive: Boolean
+ get() = error("")
+ override val list: NodeList?
+ get() = error("")
+ }
+
+ private fun String.wrap() = Wrapper(this)
+ private fun Wrapper.unwrap() = value
+
+ private fun CoroutineScope.wrappedCurrentDispatcher() = object : CoroutineDispatcher() {
+ val dispatcher = coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ dispatcher.dispatch(context, block)
+ }
+
+ @ExperimentalCoroutinesApi
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+ return dispatcher.isDispatchNeeded(context)
+ }
+
+ @InternalCoroutinesApi
+ override fun dispatchYield(context: CoroutineContext, block: Runnable) {
+ dispatcher.dispatchYield(context, block)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/CoroutineExceptionHandlerTest.kt b/kotlinx-coroutines-core/common/test/CoroutineExceptionHandlerTest.kt
new file mode 100644
index 00000000..95e93664
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CoroutineExceptionHandlerTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class CoroutineExceptionHandlerTest : TestBase() {
+ // Parent Job() does not handle exception --> handler is invoked on child crash
+ @Test
+ fun testJob() = runTest {
+ expect(1)
+ var coroutineException: Throwable? = null
+ val handler = CoroutineExceptionHandler { _, ex ->
+ coroutineException = ex
+ expect(3)
+ }
+ val parent = Job()
+ val job = launch(handler + parent) {
+ throw TestException()
+ }
+ expect(2)
+ job.join()
+ finish(4)
+ assertTrue(coroutineException is TestException)
+ assertTrue(parent.isCancelled)
+ }
+
+ // Parent CompletableDeferred() "handles" exception --> handler is NOT invoked on child crash
+ @Test
+ fun testCompletableDeferred() = runTest {
+ expect(1)
+ val handler = CoroutineExceptionHandler { _, _ ->
+ expectUnreached()
+ }
+ val parent = CompletableDeferred<Unit>()
+ val job = launch(handler + parent) {
+ throw TestException()
+ }
+ expect(2)
+ job.join()
+ finish(3)
+ assertTrue(parent.isCancelled)
+ assertTrue(parent.getCompletionExceptionOrNull() is TestException)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt b/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt
new file mode 100644
index 00000000..c46f41a0
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UNREACHABLE_CODE")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class CoroutineScopeTest : TestBase() {
+ @Test
+ fun testScope() = runTest {
+ suspend fun callJobScoped() = coroutineScope {
+ expect(2)
+ launch {
+ expect(4)
+ }
+ launch {
+ expect(5)
+
+ launch {
+ expect(7)
+ }
+
+ expect(6)
+
+ }
+ expect(3)
+ 42
+ }
+ expect(1)
+ val result = callJobScoped()
+ assertEquals(42, result)
+ yield() // Check we're not cancelled
+ finish(8)
+ }
+
+ @Test
+ fun testScopeCancelledFromWithin() = runTest {
+ expect(1)
+ suspend fun callJobScoped() = coroutineScope {
+ launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+ launch {
+ expect(3)
+ throw TestException2()
+ }
+ }
+
+ try {
+ callJobScoped()
+ expectUnreached()
+ } catch (e: TestException2) {
+ expect(4)
+ }
+ yield() // Check we're not cancelled
+ finish(5)
+ }
+
+ @Test
+ fun testExceptionFromWithin() = runTest {
+ expect(1)
+ try {
+ expect(2)
+ coroutineScope {
+ expect(3)
+ throw TestException1()
+ }
+ expectUnreached()
+ } catch (e: TestException1) {
+ finish(4)
+ }
+ }
+
+ @Test
+ fun testScopeBlockThrows() = runTest {
+ expect(1)
+ suspend fun callJobScoped(): Unit = coroutineScope {
+ launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+ yield() // let launch sleep
+ throw TestException1()
+ }
+ try {
+ callJobScoped()
+ expectUnreached()
+ } catch (e: TestException1) {
+ expect(3)
+ }
+ yield() // Check we're not cancelled
+ finish(4)
+ }
+
+ @Test
+ fun testOuterJobIsCancelled() = runTest {
+ suspend fun callJobScoped() = coroutineScope {
+ launch {
+ expect(3)
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(4)
+ }
+ }
+
+ expect(2)
+ delay(Long.MAX_VALUE)
+ 42
+ }
+
+ val outerJob = launch(NonCancellable) {
+ expect(1)
+ try {
+ callJobScoped()
+ expectUnreached()
+ } catch (e: JobCancellationException) {
+ expect(5)
+ if (RECOVER_STACK_TRACES) {
+ val cause = e.cause as JobCancellationException // shall be recovered JCE
+ assertNull(cause.cause)
+ } else {
+ assertNull(e.cause)
+ }
+ }
+ }
+ repeat(3) { yield() } // let everything to start properly
+ outerJob.cancel()
+ outerJob.join()
+ finish(6)
+ }
+
+ @Test
+ fun testAsyncCancellationFirst() = runTest {
+ try {
+ expect(1)
+ failedConcurrentSumFirst()
+ expectUnreached()
+ } catch (e: TestException1) {
+ finish(6)
+ }
+ }
+
+ // First async child fails -> second is cancelled
+ private suspend fun failedConcurrentSumFirst(): Int = coroutineScope {
+ val one = async<Int> {
+ expect(3)
+ throw TestException1()
+ }
+ val two = async(start = CoroutineStart.ATOMIC) {
+ try {
+ expect(4)
+ delay(Long.MAX_VALUE) // Emulates very long computation
+ 42
+ } finally {
+ expect(5)
+ }
+ }
+ expect(2)
+ one.await() + two.await()
+ }
+
+ @Test
+ fun testAsyncCancellationSecond() = runTest {
+ try {
+ expect(1)
+ failedConcurrentSumSecond()
+ expectUnreached()
+ } catch (e: TestException1) {
+ finish(6)
+ }
+ }
+
+ // Second async child fails -> fist is cancelled
+ private suspend fun failedConcurrentSumSecond(): Int = coroutineScope {
+ val one = async<Int> {
+ try {
+ expect(3)
+ delay(Long.MAX_VALUE) // Emulates very long computation
+ 42
+ } finally {
+ expect(5)
+ }
+ }
+ val two = async<Int>(start = CoroutineStart.ATOMIC) {
+ expect(4)
+ throw TestException1()
+ }
+ expect(2)
+ one.await() + two.await()
+ }
+
+ @Test
+ @Suppress("UNREACHABLE_CODE")
+ fun testDocumentationExample() = runTest {
+ suspend fun loadData() = coroutineScope {
+ expect(1)
+ val data = async {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(3)
+ }
+ }
+ yield()
+ // UI updater
+ withContext(coroutineContext) {
+ expect(2)
+ throw TestException1()
+ data.await() // Actually unreached
+ expectUnreached()
+ }
+ }
+
+ try {
+ loadData()
+ expectUnreached()
+ } catch (e: TestException1) {
+ finish(4)
+ }
+ }
+
+ @Test
+ fun testCoroutineScopeCancellationVsException() = runTest {
+ expect(1)
+ var job: Job? = null
+ job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ coroutineScope {
+ expect(3)
+ yield() // must suspend
+ expect(5)
+ job!!.cancel() // cancel this job _before_ it throws
+ throw TestException1()
+ }
+ } catch (e: TestException1) {
+ // must have caught TextException
+ expect(6)
+ }
+ }
+ expect(4)
+ yield() // to coroutineScope
+ finish(7)
+ }
+
+ @Test
+ fun testScopePlusContext() {
+ assertSame(EmptyCoroutineContext, scopePlusContext(EmptyCoroutineContext, EmptyCoroutineContext))
+ assertSame(Dispatchers.Default, scopePlusContext(EmptyCoroutineContext, Dispatchers.Default))
+ assertSame(Dispatchers.Default, scopePlusContext(Dispatchers.Default, EmptyCoroutineContext))
+ assertSame(Dispatchers.Default, scopePlusContext(Dispatchers.Default, Dispatchers.Default))
+ assertSame(Dispatchers.Default, scopePlusContext(Dispatchers.Unconfined, Dispatchers.Default))
+ assertSame(Dispatchers.Unconfined, scopePlusContext(Dispatchers.Default, Dispatchers.Unconfined))
+ assertSame(Dispatchers.Unconfined, scopePlusContext(Dispatchers.Unconfined, Dispatchers.Unconfined))
+ }
+
+ @Test
+ fun testIncompleteScopeState() = runTest {
+ lateinit var scopeJob: Job
+ coroutineScope {
+ scopeJob = coroutineContext[Job]!!
+ scopeJob.invokeOnCompletion { }
+ }
+
+ scopeJob.join()
+ assertTrue(scopeJob.isCompleted)
+ assertFalse(scopeJob.isActive)
+ assertFalse(scopeJob.isCancelled)
+ }
+
+ private fun scopePlusContext(c1: CoroutineContext, c2: CoroutineContext) =
+ (ContextScope(c1) + c2).coroutineContext
+}
diff --git a/kotlinx-coroutines-core/common/test/CoroutinesTest.kt b/kotlinx-coroutines-core/common/test/CoroutinesTest.kt
new file mode 100644
index 00000000..534cfd61
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CoroutinesTest.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class CoroutinesTest : TestBase() {
+
+ @Test
+ fun testSimple() = runTest {
+ expect(1)
+ finish(2)
+ }
+
+ @Test
+ fun testYield() = runTest {
+ expect(1)
+ yield() // effectively does nothing, as we don't have other coroutines
+ finish(2)
+ }
+
+ @Test
+ fun testLaunchAndYieldJoin() = runTest {
+ expect(1)
+ val job = launch {
+ expect(3)
+ yield()
+ expect(4)
+ }
+ expect(2)
+ assertTrue(job.isActive && !job.isCompleted)
+ job.join()
+ assertTrue(!job.isActive && job.isCompleted)
+ finish(5)
+ }
+
+ @Test
+ fun testLaunchUndispatched() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ yield()
+ expect(4)
+ }
+ expect(3)
+ assertTrue(job.isActive && !job.isCompleted)
+ job.join()
+ assertTrue(!job.isActive && job.isCompleted)
+ finish(5)
+ }
+
+ @Test
+ fun testNested() = runTest {
+ expect(1)
+ val j1 = launch {
+ expect(3)
+ val j2 = launch {
+ expect(5)
+ }
+ expect(4)
+ j2.join()
+ expect(6)
+ }
+ expect(2)
+ j1.join()
+ finish(7)
+ }
+
+ @Test
+ fun testWaitChild() = runTest {
+ expect(1)
+ launch {
+ expect(3)
+ yield() // to parent
+ finish(5)
+ }
+ expect(2)
+ yield()
+ expect(4)
+ // parent waits for child's completion
+ }
+
+ @Test
+ fun testCancelChildExplicit() = runTest {
+ expect(1)
+ val job = launch {
+ expect(3)
+ yield()
+ expectUnreached()
+ }
+ expect(2)
+ yield()
+ expect(4)
+ job.cancel()
+ finish(5)
+ }
+
+ @Test
+ fun testCancelChildWithFinally() = runTest {
+ expect(1)
+ val job = launch {
+ expect(3)
+ try {
+ yield()
+ } finally {
+ finish(6) // cancelled child will still execute finally
+ }
+ expectUnreached()
+ }
+ expect(2)
+ yield()
+ expect(4)
+ job.cancel()
+ expect(5)
+ }
+
+ @Test
+ fun testWaitNestedChild() = runTest {
+ expect(1)
+ launch {
+ expect(3)
+ launch {
+ expect(6)
+ yield() // to parent
+ expect(9)
+ }
+ expect(4)
+ yield()
+ expect(7)
+ yield() // to parent
+ finish(10) // the last one to complete
+ }
+ expect(2)
+ yield()
+ expect(5)
+ yield()
+ expect(8)
+ // parent waits for child
+ }
+
+ @Test
+ fun testExceptionPropagation() = runTest(
+ expected = { it is TestException }
+ ) {
+ finish(1)
+ throw TestException()
+ }
+
+ @Test
+ fun testCancelParentOnChildException() = runTest(expected = { it is TestException }) {
+ expect(1)
+ launch {
+ finish(3)
+ throwTestException() // does not propagate exception to launch, but cancels parent (!)
+ expectUnreached()
+ }
+ expect(2)
+ yield()
+ expectUnreached() // because of exception in child
+ }
+
+ @Test
+ fun testCancelParentOnNestedException() = runTest(expected = { it is TestException }) {
+ expect(1)
+ launch {
+ expect(3)
+ launch {
+ finish(6)
+ throwTestException() // unhandled exception kills all parents
+ expectUnreached()
+ }
+ expect(4)
+ yield()
+ expectUnreached() // because of exception in child
+ }
+ expect(2)
+ yield()
+ expect(5)
+ yield()
+ expectUnreached() // because of exception in child
+ }
+
+ @Test
+ fun testJoinWithFinally() = runTest {
+ expect(1)
+ val job = launch {
+ expect(3)
+ try {
+ yield() // to main, will cancel us
+ } finally {
+ expect(7) // join is waiting
+ }
+ }
+ expect(2)
+ yield() // to job
+ expect(4)
+ assertTrue(job.isActive && !job.isCompleted)
+ job.cancel() // cancels job
+ expect(5) // still here
+ assertTrue(!job.isActive && !job.isCompleted)
+ expect(6) // we're still here
+ job.join() // join the job, let job complete its "finally" section
+ expect(8)
+ assertTrue(!job.isActive && job.isCompleted)
+ finish(9)
+ }
+
+ @Test
+ fun testCancelAndJoin() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ yield()
+ expectUnreached() // will get cancelled
+ } finally {
+ expect(4)
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(5)
+ }
+
+ @Test
+ fun testCancelAndJoinChildCrash() = runTest(expected = { it is TestException }) {
+ expect(1)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ throwTestException()
+ expectUnreached()
+ }
+ // now we have a failed job with TestException
+ finish(3)
+ try {
+ job.cancelAndJoin() // join should crash on child's exception but it will be wrapped into CancellationException
+ } catch (e: Throwable) {
+ e as CancellationException // type assertion
+ assertTrue(e.cause is TestException)
+ throw e
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testYieldInFinally() = runTest(
+ expected = { it is TestException }
+ ) {
+ expect(1)
+ try {
+ expect(2)
+ throwTestException()
+ } finally {
+ expect(3)
+ yield()
+ finish(4)
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testCancelAndJoinChildren() = runTest {
+ expect(1)
+ val parent = Job()
+ launch(parent, CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ yield() // to be cancelled
+ } finally {
+ expect(5)
+ }
+ expectUnreached()
+ }
+ expect(3)
+ parent.cancelChildren()
+ expect(4)
+ parent.children.forEach { it.join() } // will yield to child
+ assertTrue(parent.isActive) // make sure it did not cancel parent
+ finish(6)
+ }
+
+ @Test
+ fun testParentCrashCancelsChildren() = runTest(
+ unhandled = listOf({ it -> it is TestException })
+ ) {
+ expect(1)
+ val parent = launch(Job()) {
+ expect(4)
+ throw TestException("Crashed")
+ }
+ val child = launch(parent, CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ yield() // to test
+ } finally {
+ expect(5)
+ withContext(NonCancellable) { yield() } // to test
+ expect(7)
+ }
+ expectUnreached() // will get cancelled, because parent crashes
+ }
+ expect(3)
+ yield() // to parent
+ expect(6)
+ parent.join() // make sure crashed parent still waits for its child
+ finish(8)
+ // make sure is cancelled
+ assertTrue(child.isCancelled)
+ }
+
+ @Test
+ fun testNotCancellableChildWithExceptionCancelled() = runTest(
+ expected = { it is TestException }
+ ) {
+ expect(1)
+ // CoroutineStart.ATOMIC makes sure it will not get cancelled for it starts executing
+ val d = async(NonCancellable, start = CoroutineStart.ATOMIC) {
+ finish(4)
+ throwTestException() // will throw
+ expectUnreached()
+ }
+ expect(2)
+ // now cancel with some other exception
+ d.cancel(TestCancellationException())
+ // now await to see how it got crashed -- TestCancellationException should have been suppressed by TestException
+ expect(3)
+ d.await()
+ }
+
+ private fun throwTestException() { throw TestException() }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/DelayTest.kt b/kotlinx-coroutines-core/common/test/DelayTest.kt
new file mode 100644
index 00000000..1b731462
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/DelayTest.kt
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED", "DEPRECATION") // KT-21913
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class DelayTest : TestBase() {
+
+ @Test
+ fun testCancellation() = runTest(expected = {it is CancellationException }) {
+ runAndCancel(1000)
+ }
+
+ @Test
+ fun testMaxLongValue()= runTest(expected = {it is CancellationException }) {
+ runAndCancel(Long.MAX_VALUE)
+ }
+
+ @Test
+ fun testMaxIntValue()= runTest(expected = {it is CancellationException }) {
+ runAndCancel(Int.MAX_VALUE.toLong())
+ }
+
+ @Test
+ fun testRegularDelay() = runTest {
+ val deferred = async {
+ expect(2)
+ delay(1)
+ expect(3)
+ }
+
+ expect(1)
+ yield()
+ deferred.await()
+ finish(4)
+ }
+
+ private suspend fun runAndCancel(time: Long) = coroutineScope {
+ expect(1)
+ val deferred = async {
+ expect(2)
+ delay(time)
+ expectUnreached()
+ }
+
+ yield()
+ expect(3)
+ require(deferred.isActive)
+ deferred.cancel()
+ finish(4)
+ deferred.await()
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/EnsureActiveTest.kt b/kotlinx-coroutines-core/common/test/EnsureActiveTest.kt
new file mode 100644
index 00000000..716be629
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/EnsureActiveTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class EnsureActiveTest : TestBase() {
+
+ private val job = Job()
+ private val scope = CoroutineScope(job + CoroutineExceptionHandler { _, _ -> })
+
+ @Test
+ fun testIsActive() = runTest {
+ expect(1)
+ scope.launch(Dispatchers.Unconfined) {
+ ensureActive()
+ coroutineContext.ensureActive()
+ coroutineContext[Job]!!.ensureActive()
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ expect(3)
+ job.ensureActive()
+ scope.ensureActive()
+ scope.coroutineContext.ensureActive()
+ job.cancelAndJoin()
+ finish(4)
+ }
+
+ @Test
+ fun testIsCompleted() = runTest {
+ expect(1)
+ scope.launch(Dispatchers.Unconfined) {
+ ensureActive()
+ coroutineContext.ensureActive()
+ coroutineContext[Job]!!.ensureActive()
+ expect(2)
+ }
+
+ expect(3)
+ job.complete()
+ job.join()
+ assertFailsWith<JobCancellationException> { job.ensureActive() }
+ assertFailsWith<JobCancellationException> { scope.ensureActive() }
+ assertFailsWith<JobCancellationException> { scope.coroutineContext.ensureActive() }
+ finish(4)
+ }
+
+
+ @Test
+ fun testIsCancelled() = runTest {
+ expect(1)
+ scope.launch(Dispatchers.Unconfined) {
+ ensureActive()
+ coroutineContext.ensureActive()
+ coroutineContext[Job]!!.ensureActive()
+ expect(2)
+ throw TestException()
+ }
+
+ expect(3)
+ checkException { job.ensureActive() }
+ checkException { scope.ensureActive() }
+ checkException { scope.coroutineContext.ensureActive() }
+ finish(4)
+ }
+
+ private inline fun checkException(block: () -> Unit) {
+ val result = runCatching(block)
+ val exception = result.exceptionOrNull() ?: fail()
+ assertTrue(exception is JobCancellationException)
+ assertTrue(exception.cause is TestException)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/ExperimentalDispatchModeTest.kt b/kotlinx-coroutines-core/common/test/ExperimentalDispatchModeTest.kt
new file mode 100644
index 00000000..37e61824
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/ExperimentalDispatchModeTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class ExperimentalDispatchModeTest : TestBase() {
+ @Test
+ fun testUnconfinedCancellation() = runTest {
+ val parent = Job()
+ launch(parent) {
+ expect(1)
+ parent.cancel()
+ launch(Dispatchers.Unconfined) {
+ expectUnreached()
+ }
+
+ }.join()
+ finish(2)
+ }
+
+ @Test
+ fun testUnconfinedCancellationState() = runTest {
+ val parent = Job()
+ launch(parent) {
+ expect(1)
+ parent.cancel()
+ val job = launch(Dispatchers.Unconfined) {
+ expectUnreached()
+ }
+
+ assertTrue(job.isCancelled)
+ assertTrue(job.isCompleted)
+ assertFalse(job.isActive)
+ }.join()
+ finish(2)
+ }
+
+ @Test
+ fun testUnconfinedCancellationLazy() = runTest {
+ val parent = Job()
+ launch(parent) {
+ expect(1)
+ val job = launch(Dispatchers.Unconfined, start = CoroutineStart.LAZY) {
+ expectUnreached()
+ }
+ job.invokeOnCompletion { expect(2) }
+ assertFalse(job.isCompleted)
+
+ parent.cancel()
+ job.join()
+ }.join()
+ finish(3)
+ }
+
+ @Test
+ fun testUndispatchedCancellation() = runTest {
+ val parent = Job()
+ launch(parent) {
+ expect(1)
+ parent.cancel()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ yield()
+ expectUnreached()
+ }
+
+ }.join()
+ finish(3)
+ }
+
+ @Test
+ fun testCancelledAtomicUnconfined() = runTest {
+ val parent = Job()
+ launch(parent) {
+ expect(1)
+ parent.cancel()
+ launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) {
+ expect(2)
+ yield()
+ expectUnreached()
+ }
+ }.join()
+ finish(3)
+ }
+
+
+ @Test
+ fun testCancelledWithContextUnconfined() = runTest {
+ val parent = Job()
+ launch(parent) {
+ expect(1)
+ parent.cancel()
+ withContext(Dispatchers.Unconfined) {
+ expectUnreached()
+ }
+ }.join()
+ finish(2)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/FailedJobTest.kt b/kotlinx-coroutines-core/common/test/FailedJobTest.kt
new file mode 100644
index 00000000..e4d0fad2
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/FailedJobTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+// see https://github.com/Kotlin/kotlinx.coroutines/issues/585
+class FailedJobTest : TestBase() {
+ @Test
+ fun testCancelledJob() = runTest {
+ expect(1)
+ val job = launch {
+ expectUnreached()
+ }
+ expect(2)
+ job.cancelAndJoin()
+ finish(3)
+ assertTrue(job.isCompleted)
+ assertTrue(!job.isActive)
+ assertTrue(job.isCancelled)
+ }
+
+ @Test
+ fun testFailedJob() = runTest(
+ unhandled = listOf({it -> it is TestException })
+ ) {
+ expect(1)
+ val job = launch(NonCancellable) {
+ expect(3)
+ throw TestException()
+ }
+ expect(2)
+ job.join()
+ finish(4)
+ assertTrue(job.isCompleted)
+ assertTrue(!job.isActive)
+ assertTrue(job.isCancelled)
+ }
+
+ @Test
+ fun testFailedChildJob() = runTest(
+ unhandled = listOf({it -> it is TestException })
+ ) {
+ expect(1)
+ val job = launch(NonCancellable) {
+ expect(3)
+ launch {
+ throw TestException()
+ }
+ }
+ expect(2)
+ job.join()
+ finish(4)
+ assertTrue(job.isCompleted)
+ assertTrue(!job.isActive)
+ assertTrue(job.isCancelled)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/JobStatesTest.kt b/kotlinx-coroutines-core/common/test/JobStatesTest.kt
new file mode 100644
index 00000000..dfcb462c
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/JobStatesTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+/**
+ * Tests that the transitions to the state of the [Job] correspond to documentation in the
+ * table that is presented in the [Job] documentation.
+ */
+class JobStatesTest : TestBase() {
+ @Test
+ public fun testNormalCompletion() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.LAZY) {
+ expect(2)
+ // launches child
+ launch {
+ expect(4)
+ }
+ // completes normally
+ }
+ // New job
+ assertFalse(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // New -> Active
+ job.start()
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // Active -> Completing
+ yield() // scheduled & starts child
+ expect(3)
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // Completing -> Completed
+ yield()
+ finish(5)
+ assertFalse(job.isActive)
+ assertTrue(job.isCompleted)
+ assertFalse(job.isCancelled)
+ }
+
+ @Test
+ public fun testCompletingFailed() = runTest(
+ unhandled = listOf({ it -> it is TestException })
+ ) {
+ expect(1)
+ val job = launch(NonCancellable, start = CoroutineStart.LAZY) {
+ expect(2)
+ // launches child
+ launch {
+ expect(4)
+ throw TestException()
+ }
+ // completes normally
+ }
+ // New job
+ assertFalse(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // New -> Active
+ job.start()
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // Active -> Completing
+ yield() // scheduled & starts child
+ expect(3)
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // Completing -> Cancelled
+ yield()
+ finish(5)
+ assertFalse(job.isActive)
+ assertTrue(job.isCompleted)
+ assertTrue(job.isCancelled)
+ }
+
+ @Test
+ public fun testFailed() = runTest(
+ unhandled = listOf({ it -> it is TestException })
+ ) {
+ expect(1)
+ val job = launch(NonCancellable, start = CoroutineStart.LAZY) {
+ expect(2)
+ // launches child
+ launch(start = CoroutineStart.ATOMIC) {
+ expect(4)
+ }
+ // failing
+ throw TestException()
+ }
+ // New job
+ assertFalse(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // New -> Active
+ job.start()
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // Active -> Cancelling
+ yield() // scheduled & starts child
+ expect(3)
+ assertFalse(job.isActive)
+ assertFalse(job.isCompleted)
+ assertTrue(job.isCancelled)
+ // Cancelling -> Cancelled
+ yield()
+ finish(5)
+ assertFalse(job.isActive)
+ assertTrue(job.isCompleted)
+ assertTrue(job.isCancelled)
+ }
+
+ @Test
+ public fun testCancelling() = runTest {
+ expect(1)
+ val job = launch(NonCancellable, start = CoroutineStart.LAZY) {
+ expect(2)
+ // launches child
+ launch(start = CoroutineStart.ATOMIC) {
+ expect(4)
+ }
+ // completes normally
+ }
+ // New job
+ assertFalse(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // New -> Active
+ job.start()
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // Active -> Completing
+ yield() // scheduled & starts child
+ expect(3)
+ assertTrue(job.isActive)
+ assertFalse(job.isCompleted)
+ assertFalse(job.isCancelled)
+ // Completing -> Cancelling
+ job.cancel()
+ assertFalse(job.isActive)
+ assertFalse(job.isCompleted)
+ assertTrue(job.isCancelled)
+ // Cancelling -> Cancelled
+ yield()
+ finish(5)
+ assertFalse(job.isActive)
+ assertTrue(job.isCompleted)
+ assertTrue(job.isCancelled)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/JobTest.kt b/kotlinx-coroutines-core/common/test/JobTest.kt
new file mode 100644
index 00000000..04d3c9e0
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/JobTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class JobTest : TestBase() {
+ @Test
+ fun testState() {
+ val job = Job()
+ assertTrue(job.isActive)
+ job.cancel()
+ assertTrue(!job.isActive)
+ }
+
+ @Test
+ fun testHandler() {
+ val job = Job()
+ var fireCount = 0
+ job.invokeOnCompletion { fireCount++ }
+ assertTrue(job.isActive)
+ assertEquals(0, fireCount)
+ // cancel once
+ job.cancel()
+ assertTrue(!job.isActive)
+ assertEquals(1, fireCount)
+ // cancel again
+ job.cancel()
+ assertTrue(!job.isActive)
+ assertEquals(1, fireCount)
+ }
+
+ @Test
+ fun testManyHandlers() {
+ val job = Job()
+ val n = 100 * stressTestMultiplier
+ val fireCount = IntArray(n)
+ for (i in 0 until n) job.invokeOnCompletion { fireCount[i]++ }
+ assertTrue(job.isActive)
+ for (i in 0 until n) assertEquals(0, fireCount[i])
+ // cancel once
+ job.cancel()
+ assertTrue(!job.isActive)
+ for (i in 0 until n) assertEquals(1, fireCount[i])
+ // cancel again
+ job.cancel()
+ assertTrue(!job.isActive)
+ for (i in 0 until n) assertEquals(1, fireCount[i])
+ }
+
+ @Test
+ fun testUnregisterInHandler() {
+ val job = Job()
+ val n = 100 * stressTestMultiplier
+ val fireCount = IntArray(n)
+ for (i in 0 until n) {
+ var registration: DisposableHandle? = null
+ registration = job.invokeOnCompletion {
+ fireCount[i]++
+ registration!!.dispose()
+ }
+ }
+ assertTrue(job.isActive)
+ for (i in 0 until n) assertEquals(0, fireCount[i])
+ // cancel once
+ job.cancel()
+ assertTrue(!job.isActive)
+ for (i in 0 until n) assertEquals(1, fireCount[i])
+ // cancel again
+ job.cancel()
+ assertTrue(!job.isActive)
+ for (i in 0 until n) assertEquals(1, fireCount[i])
+ }
+
+ @Test
+ fun testManyHandlersWithUnregister() {
+ val job = Job()
+ val n = 100 * stressTestMultiplier
+ val fireCount = IntArray(n)
+ val registrations = Array<DisposableHandle>(n) { i -> job.invokeOnCompletion { fireCount[i]++ } }
+ assertTrue(job.isActive)
+ fun unreg(i: Int) = i % 4 <= 1
+ for (i in 0 until n) if (unreg(i)) registrations[i].dispose()
+ for (i in 0 until n) assertEquals(0, fireCount[i])
+ job.cancel()
+ assertTrue(!job.isActive)
+ for (i in 0 until n) assertEquals(if (unreg(i)) 0 else 1, fireCount[i])
+ }
+
+ @Test
+ fun testExceptionsInHandler() {
+ val job = Job()
+ val n = 100 * stressTestMultiplier
+ val fireCount = IntArray(n)
+ for (i in 0 until n) job.invokeOnCompletion {
+ fireCount[i]++
+ throw TestException()
+ }
+ assertTrue(job.isActive)
+ for (i in 0 until n) assertEquals(0, fireCount[i])
+ val tryCancel = Try { job.cancel() }
+ assertTrue(!job.isActive)
+ for (i in 0 until n) assertEquals(1, fireCount[i])
+ assertTrue(tryCancel.exception is CompletionHandlerException)
+ assertTrue(tryCancel.exception!!.cause is TestException)
+ }
+
+ @Test
+ fun testCancelledParent() {
+ val parent = Job()
+ parent.cancel()
+ assertTrue(!parent.isActive)
+ val child = Job(parent)
+ assertTrue(!child.isActive)
+ }
+
+ @Test
+ fun testDisposeSingleHandler() {
+ val job = Job()
+ var fireCount = 0
+ val handler = job.invokeOnCompletion { fireCount++ }
+ handler.dispose()
+ job.cancel()
+ assertEquals(0, fireCount)
+ }
+
+ @Test
+ fun testDisposeMultipleHandler() {
+ val job = Job()
+ val handlerCount = 10
+ var fireCount = 0
+ val handlers = Array(handlerCount) { job.invokeOnCompletion { fireCount++ } }
+ handlers.forEach { it.dispose() }
+ job.cancel()
+ assertEquals(0, fireCount)
+ }
+
+ @Test
+ fun testCancelAndJoinParentWaitChildren() = runTest {
+ expect(1)
+ val parent = Job()
+ launch(parent, start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ yield() // will get cancelled
+ } finally {
+ expect(5)
+ }
+ }
+ expect(3)
+ parent.cancel()
+ expect(4)
+ parent.join()
+ finish(6)
+ }
+
+ @Test
+ fun testOnCancellingHandler() = runTest {
+ val job = launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ job.invokeOnCompletion(onCancelling = true) {
+ assertNotNull(it)
+ expect(3)
+ }
+
+ expect(1)
+ yield()
+ job.cancelAndJoin()
+ finish(4)
+ }
+
+ @Test
+ fun testOverriddenParent() = runTest {
+ val parent = Job()
+ val deferred = launch(parent, CoroutineStart.ATOMIC) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ parent.cancel()
+ expect(1)
+ deferred.join()
+ finish(3)
+ }
+
+ @Test
+ fun testJobWithParentCancelNormally() {
+ val parent = Job()
+ val job = Job(parent)
+ job.cancel()
+ assertTrue(job.isCancelled)
+ assertFalse(parent.isCancelled)
+ }
+
+ @Test
+ fun testJobWithParentCancelException() {
+ val parent = Job()
+ val job = Job(parent)
+ job.completeExceptionally(TestException())
+ assertTrue(job.isCancelled)
+ assertTrue(parent.isCancelled)
+ }
+
+ @Test
+ fun testIncompleteJobState() = runTest {
+ val job = launch {
+ coroutineContext[Job]!!.invokeOnCompletion { }
+ }
+
+ job.join()
+ assertTrue(job.isCompleted)
+ assertFalse(job.isActive)
+ assertFalse(job.isCancelled)
+ }
+
+ @Test
+ fun testChildrenWithIncompleteState() = runTest {
+ val job = async { Wrapper() }
+ job.join()
+ assertTrue(job.children.toList().isEmpty())
+ }
+
+ private class Wrapper : Incomplete {
+ override val isActive: Boolean
+ get() = error("")
+ override val list: NodeList?
+ get() = error("")
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/LaunchLazyTest.kt b/kotlinx-coroutines-core/common/test/LaunchLazyTest.kt
new file mode 100644
index 00000000..1ed466d6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/LaunchLazyTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class LaunchLazyTest : TestBase() {
+ @Test
+ fun testLaunchAndYieldJoin() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.LAZY) {
+ expect(4)
+ yield() // does nothing -- main waits
+ expect(5)
+ }
+ expect(2)
+ yield() // does nothing, was not started yet
+ expect(3)
+ assertTrue(!job.isActive && !job.isCompleted)
+ job.join()
+ assertTrue(!job.isActive && job.isCompleted)
+ finish(6)
+ }
+
+ @Test
+ fun testStart() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.LAZY) {
+ expect(5)
+ yield() // yields back to main
+ expect(7)
+ }
+ expect(2)
+ yield() // does nothing, was not started yet
+ expect(3)
+ assertTrue(!job.isActive && !job.isCompleted)
+ assertTrue(job.start())
+ assertTrue(job.isActive && !job.isCompleted)
+ assertTrue(!job.start()) // start again -- does nothing
+ assertTrue(job.isActive && !job.isCompleted)
+ expect(4)
+ yield() // now yield to started coroutine
+ expect(6)
+ assertTrue(job.isActive && !job.isCompleted)
+ yield() // yield again
+ assertTrue(!job.isActive && job.isCompleted) // it completes this time
+ expect(8)
+ job.join() // immediately returns
+ finish(9)
+ }
+
+ @Test
+ fun testInvokeOnCompletionAndStart() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.LAZY) {
+ expect(5)
+ }
+ yield() // no started yet!
+ expect(2)
+ job.invokeOnCompletion {
+ expect(6)
+ }
+ expect(3)
+ job.start()
+ expect(4)
+ yield()
+ finish(7)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/NonCancellableTest.kt b/kotlinx-coroutines-core/common/test/NonCancellableTest.kt
new file mode 100644
index 00000000..07c3f9b7
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/NonCancellableTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class NonCancellableTest : TestBase() {
+ @Test
+ fun testNonCancellable() = runTest {
+ expect(1)
+ val job = async {
+ withContext(NonCancellable) {
+ expect(2)
+ yield()
+ expect(4)
+ }
+
+ expect(5)
+ yield()
+ expectUnreached()
+ }
+
+ yield()
+ job.cancel()
+ expect(3)
+ assertTrue(job.isCancelled)
+ try {
+ job.await()
+ expectUnreached()
+ } catch (e: JobCancellationException) {
+ if (RECOVER_STACK_TRACES) {
+ val cause = e.cause as JobCancellationException // shall be recovered JCE
+ assertNull(cause.cause)
+ } else {
+ assertNull(e.cause)
+ }
+ finish(6)
+ }
+ }
+
+ @Test
+ fun testNonCancellableWithException() = runTest {
+ expect(1)
+ val deferred = async(NonCancellable) {
+ withContext(NonCancellable) {
+ expect(2)
+ yield()
+ expect(4)
+ }
+
+ expect(5)
+ yield()
+ expectUnreached()
+ }
+
+ yield()
+ deferred.cancel(TestCancellationException("TEST"))
+ expect(3)
+ assertTrue(deferred.isCancelled)
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: TestCancellationException) {
+ assertEquals("TEST", e.message)
+ finish(6)
+ }
+ }
+
+ @Test
+ fun testNonCancellableFinally() = runTest {
+ expect(1)
+ val job = async {
+ try {
+ expect(2)
+ yield()
+ expectUnreached()
+ } finally {
+ withContext(NonCancellable) {
+ expect(4)
+ yield()
+ expect(5)
+ }
+ }
+
+ expectUnreached()
+ }
+
+ yield()
+ job.cancel()
+ expect(3)
+ assertTrue(job.isCancelled)
+
+ try {
+ job.await()
+ expectUnreached()
+ } catch (e: CancellationException) {
+ finish(6)
+ }
+ }
+
+ @Test
+ fun testNonCancellableTwice() = runTest {
+ expect(1)
+ val job = async {
+ withContext(NonCancellable) {
+ expect(2)
+ yield()
+ expect(4)
+ }
+
+ withContext(NonCancellable) {
+ expect(5)
+ yield()
+ expect(6)
+ }
+ }
+
+ yield()
+ job.cancel()
+ expect(3)
+ assertTrue(job.isCancelled)
+ try {
+ job.await()
+ expectUnreached()
+ } catch (e: JobCancellationException) {
+ if (RECOVER_STACK_TRACES) {
+ val cause = e.cause as JobCancellationException // shall be recovered JCE
+ assertNull(cause.cause)
+ } else {
+ assertNull(e.cause)
+ }
+ finish(7)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt b/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt
new file mode 100644
index 00000000..96c5cf3f
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.channels.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+/**
+ * Systematically tests that various builders cancel parent on failure.
+ */
+class ParentCancellationTest : TestBase() {
+ @Test
+ fun testJobChild() = runTest {
+ testParentCancellation(expectUnhandled = false) { fail ->
+ val child = Job(coroutineContext[Job])
+ CoroutineScope(coroutineContext + child).fail()
+ }
+ }
+
+ @Test
+ fun testSupervisorJobChild() = runTest {
+ testParentCancellation(expectParentActive = true, expectUnhandled = true) { fail ->
+ val child = SupervisorJob(coroutineContext[Job])
+ CoroutineScope(coroutineContext + child).fail()
+ }
+ }
+
+ @Test
+ fun testCompletableDeferredChild() = runTest {
+ testParentCancellation { fail ->
+ val child = CompletableDeferred<Unit>(coroutineContext[Job])
+ CoroutineScope(coroutineContext + child).fail()
+ }
+ }
+
+ @Test
+ fun testLaunchChild() = runTest {
+ testParentCancellation(runsInScopeContext = true) { fail ->
+ launch { fail() }
+ }
+ }
+
+ @Test
+ fun testAsyncChild() = runTest {
+ testParentCancellation(runsInScopeContext = true) { fail ->
+ async { fail() }
+ }
+ }
+
+ @Test
+ fun testProduceChild() = runTest {
+ testParentCancellation(runsInScopeContext = true) { fail ->
+ produce<Unit> { fail() }
+ }
+ }
+
+ @Test
+ fun testBroadcastChild() = runTest {
+ testParentCancellation(runsInScopeContext = true) { fail ->
+ broadcast<Unit> { fail() }.openSubscription()
+ }
+ }
+
+ @Test
+ fun testSupervisorChild() = runTest {
+ testParentCancellation(expectParentActive = true, expectUnhandled = true, runsInScopeContext = true) { fail ->
+ supervisorScope { fail() }
+ }
+ }
+
+ @Test
+ fun testCoroutineScopeChild() = runTest {
+ testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail ->
+ coroutineScope { fail() }
+ }
+ }
+
+ @Test
+ fun testWithContextChild() = runTest {
+ testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail ->
+ withContext(CoroutineName("fail")) { fail() }
+ }
+ }
+
+ @Test
+ fun testWithTimeoutChild() = runTest {
+ testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail ->
+ withTimeout(1000) { fail() }
+ }
+ }
+
+ private suspend fun CoroutineScope.testParentCancellation(
+ expectParentActive: Boolean = false,
+ expectRethrows: Boolean = false,
+ expectUnhandled: Boolean = false,
+ runsInScopeContext: Boolean = false,
+ child: suspend CoroutineScope.(block: suspend CoroutineScope.() -> Unit) -> Unit
+ ) {
+ testWithException(
+ expectParentActive,
+ expectRethrows,
+ expectUnhandled,
+ runsInScopeContext,
+ TestException(),
+ child
+ )
+ testWithException(
+ true,
+ expectRethrows,
+ false,
+ runsInScopeContext,
+ CancellationException("Test"),
+ child
+ )
+ }
+
+ private suspend fun CoroutineScope.testWithException(
+ expectParentActive: Boolean,
+ expectRethrows: Boolean,
+ expectUnhandled: Boolean,
+ runsInScopeContext: Boolean,
+ throwException: Throwable,
+ child: suspend CoroutineScope.(block: suspend CoroutineScope.() -> Unit) -> Unit
+ ) {
+ reset()
+ expect(1)
+ val parent = CompletableDeferred<Unit>() // parent that handles exception (!)
+ val scope = CoroutineScope(coroutineContext + parent)
+ try {
+ scope.child {
+ // launch failing grandchild
+ var unhandledException: Throwable? = null
+ val handler = CoroutineExceptionHandler { _, e -> unhandledException = e }
+ val grandchild = launch(handler) {
+ throw throwException
+ }
+ grandchild.join()
+ when {
+ !expectParentActive && runsInScopeContext -> expectUnreached()
+ expectUnhandled -> assertSame(throwException, unhandledException)
+ else -> assertNull(unhandledException)
+ }
+ }
+ if (expectRethrows && throwException !is CancellationException) {
+ expectUnreached()
+ } else {
+ expect(2)
+ }
+ } catch (e: Throwable) {
+ if (expectRethrows) {
+ expect(2)
+ assertSame(throwException, e)
+ } else {
+ expectUnreached()
+ }
+ }
+ if (expectParentActive) {
+ assertTrue(parent.isActive)
+ } else {
+ parent.join()
+ assertFalse(parent.isActive)
+ assertTrue(parent.isCancelled)
+ }
+ finish(3)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/SupervisorTest.kt b/kotlinx-coroutines-core/common/test/SupervisorTest.kt
new file mode 100644
index 00000000..535073e0
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/SupervisorTest.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class SupervisorTest : TestBase() {
+ @Test
+ fun testSupervisorJob() = runTest(
+ unhandled = listOf(
+ { it -> it is TestException2 },
+ { it -> it is TestException1 }
+ )
+ ) {
+ expect(1)
+ val supervisor = SupervisorJob()
+ val job1 = launch(supervisor + CoroutineName("job1")) {
+ expect(2)
+ yield() // to second child
+ expect(4)
+ throw TestException1()
+ }
+ val job2 = launch(supervisor + CoroutineName("job2")) {
+ expect(3)
+ throw TestException2()
+ }
+ joinAll(job1, job2)
+ finish(5)
+ assertTrue(job1.isCancelled)
+ assertTrue(job2.isCancelled)
+ assertFalse(supervisor.isCancelled)
+ assertFalse(supervisor.isCompleted)
+ }
+
+ @Test
+ fun testSupervisorScope() = runTest(
+ unhandled = listOf(
+ { it -> it is TestException1 },
+ { it -> it is TestException2 }
+ )
+ ) {
+ val result = supervisorScope {
+ launch {
+ throw TestException1()
+ }
+ launch {
+ throw TestException2()
+ }
+ "OK"
+ }
+ assertEquals("OK", result)
+ }
+
+ @Test
+ fun testSupervisorScopeIsolation() = runTest(
+ unhandled = listOf(
+ { it -> it is TestException2 })
+ ) {
+ val result = supervisorScope {
+ expect(1)
+ val job = launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ val failingJob = launch {
+ expect(3)
+ throw TestException2()
+ }
+
+ failingJob.join()
+ yield()
+ expect(4)
+ assertTrue(job.isActive)
+ assertFalse(job.isCancelled)
+ job.cancel()
+ "OK"
+ }
+ assertEquals("OK", result)
+ finish(5)
+ }
+
+ @Test
+ fun testThrowingSupervisorScope() = runTest {
+ try {
+ expect(1)
+ supervisorScope {
+ async {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(3)
+ }
+ }
+
+ expect(2)
+ yield()
+ throw TestException2()
+ }
+ } catch (e: Throwable) {
+ finish(4)
+ }
+ }
+
+ @Test
+ fun testSupervisorThrows() = runTest {
+ try {
+ supervisorScope {
+ expect(1)
+ launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ launch {
+ expect(3)
+ delay(Long.MAX_VALUE)
+ }
+
+ yield()
+ expect(4)
+ throw TestException1()
+ }
+ } catch (e: TestException1) {
+ finish(5)
+ }
+ }
+
+ @Test
+ fun testSupervisorThrowsWithFailingChild() = runTest(unhandled = listOf({e -> e is TestException2})) {
+ try {
+ supervisorScope {
+ expect(1)
+ launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ launch {
+ expect(3)
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException2()
+ }
+ }
+
+ yield()
+ expect(4)
+ throw TestException1()
+ }
+ } catch (e: TestException1) {
+ finish(5)
+ }
+ }
+
+ @Test
+ fun testAsyncCancellation() = runTest {
+ val parent = SupervisorJob()
+ val deferred = async(parent) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+ expect(1)
+ yield()
+ parent.completeExceptionally(TestException1())
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: CancellationException) {
+ val cause = if (RECOVER_STACK_TRACES) e.cause?.cause!! else e.cause
+ assertTrue(cause is TestException1)
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testSupervisorWithParentCancelNormally() {
+ val parent = Job()
+ val supervisor = SupervisorJob(parent)
+ supervisor.cancel()
+ assertTrue(supervisor.isCancelled)
+ assertFalse(parent.isCancelled)
+ }
+
+ @Test
+ fun testSupervisorWithParentCancelException() {
+ val parent = Job()
+ val supervisor = SupervisorJob(parent)
+ supervisor.completeExceptionally(TestException1())
+ assertTrue(supervisor.isCancelled)
+ assertTrue(parent.isCancelled)
+ }
+
+ @Test
+ fun testSupervisorScopeCancellationVsException() = runTest {
+ expect(1)
+ var job: Job? = null
+ job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ supervisorScope {
+ expect(3)
+ yield() // must suspend
+ expect(5)
+ job!!.cancel() // cancel this job _before_ it throws
+ throw TestException1()
+ }
+ } catch (e: TestException1) {
+ // must have caught TextException
+ expect(6)
+ }
+ }
+ expect(4)
+ yield() // to coroutineScope
+ finish(7)
+ }
+
+ @Test
+ fun testSupervisorJobCancellationException() = runTest {
+ val job = SupervisorJob()
+ val child = launch(job + CoroutineExceptionHandler { _, _ -> expectUnreached() }) {
+ expect(1)
+ hang {
+ expect(3)
+ }
+ }
+
+ yield()
+ expect(2)
+ child.cancelAndJoin()
+ job.complete()
+ job.join()
+ finish(4)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/TestBase.common.kt b/kotlinx-coroutines-core/common/test/TestBase.common.kt
new file mode 100644
index 00000000..ad7b8b15
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/TestBase.common.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.flow.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+public expect open class TestBase constructor() {
+ public val isStressTest: Boolean
+ public val stressTestMultiplier: Int
+
+ public fun error(message: Any, cause: Throwable? = null): Nothing
+ public fun expect(index: Int)
+ public fun expectUnreached()
+ public fun finish(index: Int)
+ public fun ensureFinished() // Ensures that 'finish' was invoked
+ public fun reset() // Resets counter and finish flag. Workaround for parametrized tests absence in common
+
+ public fun runTest(
+ expected: ((Throwable) -> Boolean)? = null,
+ unhandled: List<(Throwable) -> Boolean> = emptyList(),
+ block: suspend CoroutineScope.() -> Unit
+ )
+}
+
+public suspend inline fun hang(onCancellation: () -> Unit) {
+ try {
+ suspendCancellableCoroutine<Unit> { }
+ } finally {
+ onCancellation()
+ }
+}
+
+public inline fun <reified T : Throwable> assertFailsWith(block: () -> Unit) {
+ try {
+ block()
+ error("Should not be reached")
+ } catch (e: Throwable) {
+ assertTrue(e is T)
+ }
+}
+
+public suspend inline fun <reified T : Throwable> assertFailsWith(flow: Flow<*>) {
+ try {
+ flow.collect()
+ fail("Should be unreached")
+ } catch (e: Throwable) {
+ assertTrue(e is T)
+ }
+}
+
+public suspend fun Flow<Int>.sum() = fold(0) { acc, value -> acc + value }
+public suspend fun Flow<Long>.longSum() = fold(0L) { acc, value -> acc + value }
+
+
+// data is added to avoid stacktrace recovery because CopyableThrowable is not accessible from common modules
+public class TestException(message: String? = null, private val data: Any? = null) : Throwable(message)
+public class TestException1(message: String? = null, private val data: Any? = null) : Throwable(message)
+public class TestException2(message: String? = null, private val data: Any? = null) : Throwable(message)
+public class TestException3(message: String? = null, private val data: Any? = null) : Throwable(message)
+public class TestCancellationException(message: String? = null, private val data: Any? = null) : CancellationException(message)
+public class TestRuntimeException(message: String? = null, private val data: Any? = null) : RuntimeException(message)
+public class RecoverableTestException(message: String? = null) : RuntimeException(message)
+public class RecoverableTestCancellationException(message: String? = null) : CancellationException(message)
+
+public fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
+ val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
+ return object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ dispatcher.dispatch(context, block)
+ }
+ }
+}
+
+public suspend fun wrapperDispatcher(): CoroutineContext = wrapperDispatcher(coroutineContext)
+
diff --git a/kotlinx-coroutines-core/common/test/Try.kt b/kotlinx-coroutines-core/common/test/Try.kt
new file mode 100644
index 00000000..194ea4ba
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/Try.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+public class Try<out T> private constructor(private val _value: Any?) {
+ private class Fail(val exception: Throwable) {
+ override fun toString(): String = "Failure[$exception]"
+ }
+
+ public companion object {
+ public operator fun <T> invoke(block: () -> T): Try<T> =
+ try {
+ Success(block())
+ } catch(e: Throwable) {
+ Failure<T>(e)
+ }
+ public fun <T> Success(value: T) = Try<T>(value)
+ public fun <T> Failure(exception: Throwable) = Try<T>(Fail(exception))
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ public val value: T get() = if (_value is Fail) throw _value.exception else _value as T
+
+ public val exception: Throwable? get() = (_value as? Fail)?.exception
+
+ override fun toString(): String = _value.toString()
+}
diff --git a/kotlinx-coroutines-core/common/test/UnconfinedTest.kt b/kotlinx-coroutines-core/common/test/UnconfinedTest.kt
new file mode 100644
index 00000000..4f9cc9b1
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/UnconfinedTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class UnconfinedTest : TestBase() {
+
+ @Test
+ fun testOrder() = runTest {
+ expect(1)
+ launch(Dispatchers.Unconfined) {
+ expect(2)
+ launch {
+ expect(4)
+ launch {
+ expect(6)
+ }
+
+ launch {
+ expect(7)
+ }
+ expect(5)
+ }
+
+ expect(3)
+ }
+
+ finish(8)
+ }
+
+ @Test
+ fun testBlockThrows() = runTest {
+ expect(1)
+ try {
+ withContext(Dispatchers.Unconfined) {
+ expect(2)
+ withContext(Dispatchers.Unconfined + CoroutineName("a")) {
+ expect(3)
+ }
+
+ expect(4)
+ launch(start = CoroutineStart.ATOMIC) {
+ expect(5)
+ }
+
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ finish(6)
+ }
+ }
+
+ @Test
+ fun testEnterMultipleTimes() = runTest {
+ launch(Unconfined) {
+ expect(1)
+ }
+
+ launch(Unconfined) {
+ expect(2)
+ }
+
+ launch(Unconfined) {
+ expect(3)
+ }
+
+ finish(4)
+ }
+
+ @Test
+ fun testYield() = runTest {
+ expect(1)
+ launch(Dispatchers.Unconfined) {
+ expect(2)
+ yield()
+ launch {
+ expect(4)
+ }
+ expect(3)
+ yield()
+ expect(5)
+ }.join()
+
+ finish(6)
+ }
+
+ @Test
+ fun testCancellationWihYields() = runTest {
+ expect(1)
+ GlobalScope.launch(Dispatchers.Unconfined) {
+ val job = coroutineContext[Job]!!
+ expect(2)
+ yield()
+ GlobalScope.launch(Dispatchers.Unconfined) {
+ expect(4)
+ job.cancel()
+ expect(5)
+ }
+ expect(3)
+
+ try {
+ yield()
+ } finally {
+ expect(6)
+ }
+ }
+
+ finish(7)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt b/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt
new file mode 100644
index 00000000..e2625722
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class UndispatchedResultTest : TestBase() {
+
+ @Test
+ fun testWithContext() = runTest {
+ invokeTest { block -> withContext(wrapperDispatcher(coroutineContext), block) }
+ }
+
+ @Test
+ fun testWithContextFastPath() = runTest {
+ invokeTest { block -> withContext(coroutineContext, block) }
+ }
+
+ @Test
+ fun testWithTimeout() = runTest {
+ invokeTest { block -> withTimeout(Long.MAX_VALUE, block) }
+ }
+
+ @Test
+ fun testAsync() = runTest {
+ invokeTest { block -> async(NonCancellable, block = block).await() }
+ }
+
+ @Test
+ fun testCoroutineScope() = runTest {
+ invokeTest { block -> coroutineScope(block) }
+ }
+
+ private suspend fun invokeTest(scopeProvider: suspend (suspend CoroutineScope.() -> Unit) -> Unit) {
+ invokeTest(EmptyCoroutineContext, scopeProvider)
+ invokeTest(Unconfined, scopeProvider)
+ }
+
+ private suspend fun invokeTest(
+ context: CoroutineContext,
+ scopeProvider: suspend (suspend CoroutineScope.() -> Unit) -> Unit
+ ) {
+ try {
+ scopeProvider { block(context) }
+ } catch (e: TestException) {
+ finish(5)
+ reset()
+ }
+ }
+
+ private suspend fun CoroutineScope.block(context: CoroutineContext) {
+ try {
+ expect(1)
+ // Will cancel its parent
+ async(context) {
+ expect(2)
+ throw TestException()
+ }.await()
+ } catch (e: TestException) {
+ expect(3)
+ }
+ expect(4)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/WithContextTest.kt b/kotlinx-coroutines-core/common/test/WithContextTest.kt
new file mode 100644
index 00000000..55127e5c
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/WithContextTest.kt
@@ -0,0 +1,381 @@
+
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-22237
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class WithContextTest : TestBase() {
+
+ @Test
+ fun testThrowException() = runTest {
+ expect(1)
+ try {
+ withContext<Unit>(coroutineContext) {
+ expect(2)
+ throw AssertionError()
+ }
+ } catch (e: AssertionError) {
+ expect(3)
+ }
+
+ yield()
+ finish(4)
+ }
+
+ @Test
+ fun testThrowExceptionFromWrappedContext() = runTest {
+ expect(1)
+ try {
+ withContext<Unit>(wrapperDispatcher(coroutineContext)) {
+ expect(2)
+ throw AssertionError()
+ }
+ } catch (e: AssertionError) {
+ expect(3)
+ }
+
+ yield()
+ finish(4)
+ }
+
+ @Test
+ fun testSameContextNoSuspend() = runTest {
+ expect(1)
+ launch(coroutineContext) { // make sure there is not early dispatch here
+ finish(5) // after main exits
+ }
+ expect(2)
+ val result = withContext(coroutineContext) { // same context!
+ expect(3) // still here
+ "OK".wrap()
+ }.unwrap()
+ assertEquals("OK", result)
+ expect(4)
+ // will wait for the first coroutine
+ }
+
+ @Test
+ fun testSameContextWithSuspend() = runTest {
+ expect(1)
+ launch(coroutineContext) { // make sure there is not early dispatch here
+ expect(4)
+ }
+ expect(2)
+ val result = withContext(coroutineContext) { // same context!
+ expect(3) // still here
+ yield() // now yields to launch!
+ expect(5)
+ "OK".wrap()
+ }.unwrap()
+ assertEquals("OK", result)
+ finish(6)
+ }
+
+ @Test
+ fun testCancelWithJobNoSuspend() = runTest {
+ expect(1)
+ launch(coroutineContext) { // make sure there is not early dispatch to here
+ finish(6) // after main exits
+ }
+ expect(2)
+ val job = Job()
+ try {
+ withContext(coroutineContext + job) {
+ // same context + new job
+ expect(3) // still here
+ job.cancel() // cancel out job!
+ try {
+ yield() // shall throw CancellationException
+ expectUnreached()
+ } catch (e: CancellationException) {
+ expect(4)
+ }
+ "OK".wrap()
+ }
+
+ expectUnreached()
+ } catch (e: CancellationException) {
+ expect(5)
+ // will wait for the first coroutine
+ }
+ }
+
+ @Test
+ fun testCancelWithJobWithSuspend() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ expect(1)
+ launch(coroutineContext) { // make sure there is not early dispatch to here
+ expect(4)
+ }
+ expect(2)
+ val job = Job()
+ withContext(coroutineContext + job) { // same context + new job
+ expect(3) // still here
+ yield() // now yields to launch!
+ expect(5)
+ job.cancel() // cancel out job!
+ try {
+ yield() // shall throw CancellationException
+ expectUnreached()
+ } catch (e: CancellationException) {
+ finish(6)
+ }
+ "OK".wrap()
+ }
+ // still fails, because parent job was cancelled
+ expectUnreached()
+ }
+
+ @Test
+ fun testRunCancellableDefault() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ val job = Job()
+ job.cancel() // cancel before it has a chance to run
+ withContext(job + wrapperDispatcher(coroutineContext)) {
+ expectUnreached() // will get cancelled
+ }
+ }
+
+ @Test
+ fun testRunCancellationUndispatchedVsException() = runTest {
+ expect(1)
+ var job: Job? = null
+ job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ // Same dispatcher, different context
+ withContext<Unit>(CoroutineName("testRunCancellationUndispatchedVsException")) {
+ expect(3)
+ yield() // must suspend
+ expect(5)
+ job!!.cancel() // cancel this job _before_ it throws
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ // must have caught TextException
+ expect(6)
+ }
+ }
+ expect(4)
+ yield() // to coroutineScope
+ finish(7)
+ }
+
+ @Test
+ fun testRunCancellationDispatchedVsException() = runTest {
+ expect(1)
+ var job: Job? = null
+ job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ // "Different" dispatcher (schedules execution back and forth)
+ withContext<Unit>(wrapperDispatcher(coroutineContext)) {
+ expect(4)
+ yield() // must suspend
+ expect(6)
+ job!!.cancel() // cancel this job _before_ it throws
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ // must have caught TextException
+ expect(8)
+ }
+ }
+ expect(3)
+ yield() // withContext is next
+ expect(5)
+ yield() // withContext again
+ expect(7)
+ yield() // to catch block
+ finish(9)
+ }
+
+ @Test
+ fun testRunSelfCancellationWithException() = runTest {
+ expect(1)
+ var job: Job? = null
+ job = launch(Job()) {
+ try {
+ expect(3)
+ withContext<Unit>(wrapperDispatcher(coroutineContext)) {
+ require(isActive)
+ expect(5)
+ job!!.cancel()
+ require(!isActive)
+ throw TestException() // but throw an exception
+ }
+ } catch (e: Throwable) {
+ expect(7)
+ // make sure TestException, not CancellationException is thrown
+ assertTrue(e is TestException, "Caught $e")
+ }
+ }
+ expect(2)
+ yield() // to the launched job
+ expect(4)
+ yield() // again to the job
+ expect(6)
+ yield() // again to exception handler
+ finish(8)
+ }
+
+ @Test
+ fun testRunSelfCancellation() = runTest {
+ expect(1)
+ var job: Job? = null
+ job = launch(Job()) {
+ try {
+ expect(3)
+ withContext(wrapperDispatcher(coroutineContext)) {
+ require(isActive)
+ expect(5)
+ job!!.cancel() // cancel itself
+ require(!isActive)
+ "OK".wrap()
+ }
+ expectUnreached()
+ } catch (e: Throwable) {
+ expect(7)
+ // make sure CancellationException is thrown
+ assertTrue(e is CancellationException, "Caught $e")
+ }
+ }
+
+ expect(2)
+ yield() // to the launched job
+ expect(4)
+ yield() // again to the job
+ expect(6)
+ yield() // again to exception handler
+ finish(8)
+ }
+
+ @Test
+ fun testWithContextScopeFailure() = runTest {
+ expect(1)
+ try {
+ withContext(wrapperDispatcher(coroutineContext)) {
+ expect(2)
+ // launch a child that fails
+ launch {
+ expect(4)
+ throw TestException()
+ }
+ expect(3)
+ "OK".wrap()
+ }
+ expectUnreached()
+ } catch (e: TestException) {
+ // ensure that we can catch exception outside of the scope
+ expect(5)
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testWithContextChildWaitSameContext() = runTest {
+ expect(1)
+ withContext(coroutineContext) {
+ expect(2)
+ launch {
+ // ^^^ schedules to main thread
+ expect(4) // waits before return
+ }
+ expect(3)
+ "OK".wrap()
+ }.unwrap()
+ finish(5)
+ }
+
+ @Test
+ fun testWithContextChildWaitWrappedContext() = runTest {
+ expect(1)
+ withContext(wrapperDispatcher(coroutineContext)) {
+ expect(2)
+ launch {
+ // ^^^ schedules to main thread
+ expect(4) // waits before return
+ }
+ expect(3)
+ "OK".wrap()
+ }.unwrap()
+ finish(5)
+ }
+
+ @Test
+ fun testIncompleteWithContextState() = runTest {
+ lateinit var ctxJob: Job
+ withContext(wrapperDispatcher(coroutineContext)) {
+ ctxJob = coroutineContext[Job]!!
+ ctxJob.invokeOnCompletion { }
+ }
+
+ ctxJob.join()
+ assertTrue(ctxJob.isCompleted)
+ assertFalse(ctxJob.isActive)
+ assertFalse(ctxJob.isCancelled)
+ }
+
+ @Test
+ fun testWithContextCancelledJob() = runTest {
+ expect(1)
+ val job = Job()
+ job.cancel()
+ try {
+ withContext(job) {
+ expectUnreached()
+ }
+ } catch (e: CancellationException) {
+ expect(2)
+ }
+ finish(3)
+ }
+
+ @Test
+ fun testWithContextCancelledThisJob() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ coroutineContext.cancel()
+ withContext(wrapperDispatcher(coroutineContext)) {
+ expectUnreached()
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testSequentialCancellation() = runTest {
+ val job = launch {
+ expect(1)
+ withContext(wrapperDispatcher()) {
+ expect(2)
+ }
+ expectUnreached()
+ }
+
+ yield()
+ val job2 = launch {
+ expect(3)
+ job.cancel()
+ }
+
+ joinAll(job, job2)
+ finish(4)
+ }
+
+ private class Wrapper(val value: String) : Incomplete {
+ override val isActive: Boolean
+ get() = error("")
+ override val list: NodeList?
+ get() = error("")
+ }
+
+ private fun String.wrap() = Wrapper(this)
+ private fun Wrapper.unwrap() = value
+}
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
new file mode 100644
index 00000000..5d41efc0
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
@@ -0,0 +1,241 @@
+
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.channels.*
+import kotlin.test.*
+
+class WithTimeoutOrNullTest : TestBase() {
+ /**
+ * Tests a case of no timeout and no suspension inside.
+ */
+ @Test
+ fun testBasicNoSuspend() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(10_000) {
+ expect(2)
+ "OK"
+ }
+ assertEquals("OK", result)
+ finish(3)
+ }
+
+ /**
+ * Tests a case of no timeout and one suspension inside.
+ */
+ @Test
+ fun testBasicSuspend() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(10_000) {
+ expect(2)
+ yield()
+ expect(3)
+ "OK"
+ }
+ assertEquals("OK", result)
+ finish(4)
+ }
+
+ /**
+ * Tests property dispatching of `withTimeoutOrNull` blocks
+ */
+ @Test
+ fun testDispatch() = runTest {
+ expect(1)
+ launch {
+ expect(4)
+ yield() // back to main
+ expect(7)
+ }
+ expect(2)
+ // test that it does not yield to the above job when started
+ val result = withTimeoutOrNull(1000) {
+ expect(3)
+ yield() // yield only now
+ expect(5)
+ "OK"
+ }
+ assertEquals("OK", result)
+ expect(6)
+ yield() // back to launch
+ finish(8)
+ }
+
+ /**
+ * Tests that a 100% CPU-consuming loop will react on timeout if it has yields.
+ */
+ @Test
+ fun testYieldBlockingWithTimeout() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(100) {
+ while (true) {
+ yield()
+ }
+ }
+ assertEquals(null, result)
+ finish(2)
+ }
+
+ @Test
+ fun testSmallTimeout() = runTest {
+ val channel = Channel<Int>(1)
+ val value = withTimeoutOrNull(1) {
+ channel.receive()
+ }
+ assertNull(value)
+ }
+
+ @Test
+ fun testThrowException() = runTest(expected = {it is AssertionError}) {
+ withTimeoutOrNull(Long.MAX_VALUE) {
+ throw AssertionError()
+ }
+ }
+
+ @Test
+ fun testInnerTimeout() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ withTimeoutOrNull(1000) {
+ withTimeout(10) {
+ while (true) {
+ yield()
+ }
+ }
+ expectUnreached() // will timeout
+ }
+ expectUnreached() // will timeout
+ }
+
+ @Test
+ fun testNestedTimeout() = runTest(expected = { it is TimeoutCancellationException }) {
+ withTimeoutOrNull(Long.MAX_VALUE) {
+ // Exception from this withTimeout is not suppressed by withTimeoutOrNull
+ withTimeout(10) {
+ delay(Long.MAX_VALUE)
+ 1
+ }
+ }
+
+ expectUnreached()
+ }
+
+ @Test
+ fun testOuterTimeout() = runTest {
+ var counter = 0
+ val result = withTimeoutOrNull(250) {
+ while (true) {
+ val inner = withTimeoutOrNull(100) {
+ while (true) {
+ yield()
+ }
+ }
+ assertEquals(null, inner)
+ counter++
+ }
+ }
+ assertEquals(null, result)
+ check(counter in 1..2) {"Executed: $counter times"}
+ }
+
+ @Test
+ fun testBadClass() = runTest {
+ val bad = BadClass()
+ val result = withTimeoutOrNull(100) {
+ bad
+ }
+ assertSame(bad, result)
+ }
+
+ class BadClass {
+ override fun equals(other: Any?): Boolean = error("Should not be called")
+ override fun hashCode(): Int = error("Should not be called")
+ override fun toString(): String = error("Should not be called")
+ }
+
+ @Test
+ fun testNullOnTimeout() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(100) {
+ expect(2)
+ delay(1000)
+ expectUnreached()
+ "OK"
+ }
+ assertEquals(null, result)
+ finish(3)
+ }
+
+ @Test
+ fun testSuppressExceptionWithResult() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(100) {
+ expect(2)
+ try {
+ delay(1000)
+ } catch (e: CancellationException) {
+ expect(3)
+ }
+ "OK"
+ }
+ assertEquals(null, result)
+ finish(4)
+ }
+
+ @Test
+ fun testSuppressExceptionWithAnotherException() = runTest {
+ expect(1)
+ try {
+ withTimeoutOrNull(100) {
+ expect(2)
+ try {
+ delay(1000)
+ } catch (e: CancellationException) {
+ expect(3)
+ throw TestException()
+ }
+ expectUnreached()
+ "OK"
+ }
+ expectUnreached()
+ } catch (e: TestException) {
+ // catches TestException
+ finish(4)
+
+ }
+ }
+
+ @Test
+ fun testNegativeTimeout() = runTest {
+ expect(1)
+ var result = withTimeoutOrNull(-1) {
+ expectUnreached()
+ }
+ assertNull(result)
+ result = withTimeoutOrNull(0) {
+ expectUnreached()
+ }
+ assertNull(result)
+ finish(2)
+ }
+
+ @Test
+ fun testExceptionFromWithinTimeout() = runTest {
+ expect(1)
+ try {
+ expect(2)
+ withTimeoutOrNull(1000) {
+ expect(3)
+ throw TestException()
+ }
+ expectUnreached()
+ } catch (e: TestException) {
+ finish(4)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutTest.kt
new file mode 100644
index 00000000..ab61b9c8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutTest.kt
@@ -0,0 +1,213 @@
+
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED", "UNREACHABLE_CODE") // KT-21913
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class WithTimeoutTest : TestBase() {
+ /**
+ * Tests a case of no timeout and no suspension inside.
+ */
+ @Test
+ fun testBasicNoSuspend() = runTest {
+ expect(1)
+ val result = withTimeout(10_000) {
+ expect(2)
+ "OK"
+ }
+ assertEquals("OK", result)
+ finish(3)
+ }
+
+ /**
+ * Tests a case of no timeout and one suspension inside.
+ */
+ @Test
+ fun testBasicSuspend() = runTest {
+ expect(1)
+ val result = withTimeout(10_000) {
+ expect(2)
+ yield()
+ expect(3)
+ "OK"
+ }
+ assertEquals("OK", result)
+ finish(4)
+ }
+
+ /**
+ * Tests proper dispatching of `withTimeout` blocks
+ */
+ @Test
+ fun testDispatch() = runTest {
+ expect(1)
+ launch {
+ expect(4)
+ yield() // back to main
+ expect(7)
+ }
+ expect(2)
+ // test that it does not yield to the above job when started
+ val result = withTimeout(1000) {
+ expect(3)
+ yield() // yield only now
+ expect(5)
+ "OK"
+ }
+ assertEquals("OK", result)
+ expect(6)
+ yield() // back to launch
+ finish(8)
+ }
+
+
+ /**
+ * Tests that a 100% CPU-consuming loop will react on timeout if it has yields.
+ */
+ @Test
+ fun testYieldBlockingWithTimeout() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ withTimeout(100) {
+ while (true) {
+ yield()
+ }
+ }
+ }
+
+ /**
+ * Tests that [withTimeout] waits for children coroutines to complete.
+ */
+ @Test
+ fun testWithTimeoutChildWait() = runTest {
+ expect(1)
+ withTimeout(100) {
+ expect(2)
+ // launch child with timeout
+ launch {
+ expect(4)
+ }
+ expect(3)
+ // now will wait for child before returning
+ }
+ finish(5)
+ }
+
+ @Test
+ fun testBadClass() = runTest {
+ val bad = BadClass()
+ val result = withTimeout(100) {
+ bad
+ }
+ assertSame(bad, result)
+ }
+
+ class BadClass {
+ override fun equals(other: Any?): Boolean = error("Should not be called")
+ override fun hashCode(): Int = error("Should not be called")
+ override fun toString(): String = error("Should not be called")
+ }
+
+ @Test
+ fun testExceptionOnTimeout() = runTest {
+ expect(1)
+ try {
+ withTimeout(100) {
+ expect(2)
+ delay(1000)
+ expectUnreached()
+ "OK"
+ }
+ } catch (e: CancellationException) {
+ assertEquals("Timed out waiting for 100 ms", e.message)
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testSuppressExceptionWithResult() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ expect(1)
+ withTimeout(100) {
+ expect(2)
+ try {
+ delay(1000)
+ } catch (e: CancellationException) {
+ finish(3)
+ }
+ "OK"
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testSuppressExceptionWithAnotherException() = runTest{
+ expect(1)
+ try {
+ withTimeout(100) {
+ expect(2)
+ try {
+ delay(1000)
+ } catch (e: CancellationException) {
+ expect(3)
+ throw TestException()
+ }
+ expectUnreached()
+ "OK"
+ }
+ expectUnreached()
+ } catch (e: TestException) {
+ finish(4)
+ }
+ }
+
+ @Test
+ fun testNegativeTimeout() = runTest {
+ expect(1)
+ try {
+ withTimeout(-1) {
+ expectUnreached()
+ "OK"
+ }
+ } catch (e: TimeoutCancellationException) {
+ assertEquals("Timed out immediately", e.message)
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testExceptionFromWithinTimeout() = runTest {
+ expect(1)
+ try {
+ expect(2)
+ withTimeout(1000) {
+ expect(3)
+ throw TestException()
+ }
+ expectUnreached()
+ } catch (e: TestException) {
+ finish(4)
+ }
+ }
+
+ @Test
+ fun testIncompleteWithTimeoutState() = runTest {
+ lateinit var timeoutJob: Job
+ val handle = withTimeout(Long.MAX_VALUE) {
+ timeoutJob = coroutineContext[Job]!!
+ timeoutJob.invokeOnCompletion { }
+ }
+
+ handle.dispose()
+ timeoutJob.join()
+ assertTrue(timeoutJob.isCompleted)
+ assertFalse(timeoutJob.isActive)
+ assertFalse(timeoutJob.isCancelled)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt
new file mode 100644
index 00000000..a7084296
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2016-2018 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 ArrayBroadcastChannelTest : TestBase() {
+
+ @Test
+ fun testConcurrentModification() = runTest {
+ val channel = BroadcastChannel<Int>(1)
+ val s1 = channel.openSubscription()
+ val s2 = channel.openSubscription()
+
+ val job1 = launch(Dispatchers.Unconfined, CoroutineStart.UNDISPATCHED) {
+ expect(1)
+ s1.receive()
+ s1.cancel()
+ }
+
+ val job2 = launch(Dispatchers.Unconfined, CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ s2.receive()
+ }
+
+ expect(3)
+ channel.send(1)
+ joinAll(job1, job2)
+ finish(4)
+ }
+
+ @Test
+ fun testBasic() = runTest {
+ expect(1)
+ val broadcast = BroadcastChannel<Int>(1)
+ assertFalse(broadcast.isClosedForSend)
+ val first = broadcast.openSubscription()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ assertEquals(1, first.receive()) // suspends
+ assertFalse(first.isClosedForReceive)
+ expect(5)
+ assertEquals(2, first.receive()) // suspends
+ assertFalse(first.isClosedForReceive)
+ expect(10)
+ assertNull(first.receiveOrNull()) // suspends
+ assertTrue(first.isClosedForReceive)
+ expect(14)
+ }
+ expect(3)
+ broadcast.send(1)
+ expect(4)
+ yield() // to the first receiver
+ expect(6)
+
+ val second = broadcast.openSubscription()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(7)
+ assertEquals(2, second.receive()) // suspends
+ assertFalse(second.isClosedForReceive)
+ expect(11)
+ assertNull(second.receiveOrNull()) // suspends
+ assertTrue(second.isClosedForReceive)
+ expect(15)
+ }
+ expect(8)
+ broadcast.send(2)
+ expect(9)
+ yield() // to first & second receivers
+ expect(12)
+ broadcast.close()
+ expect(13)
+ assertTrue(broadcast.isClosedForSend)
+ yield() // to first & second receivers
+ finish(16)
+ }
+
+ @Test
+ fun testSendSuspend() = runTest {
+ expect(1)
+ val broadcast = BroadcastChannel<Int>(1)
+ val first = broadcast.openSubscription()
+ launch {
+ expect(4)
+ assertEquals(1, first.receive())
+ expect(5)
+ assertEquals(2, first.receive())
+ expect(6)
+ }
+ expect(2)
+ broadcast.send(1) // puts to buffer, receiver not running yet
+ expect(3)
+ broadcast.send(2) // suspends
+ finish(7)
+ }
+
+ @Test
+ fun testConcurrentSendCompletion() = runTest {
+ expect(1)
+ val broadcast = BroadcastChannel<Int>(1)
+ val sub = broadcast.openSubscription()
+ // launch 3 concurrent senders (one goes buffer, two other suspend)
+ for (x in 1..3) {
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(x + 1)
+ broadcast.send(x)
+ }
+ }
+ // and close it for send
+ expect(5)
+ broadcast.close()
+ // now must receive all 3 items
+ expect(6)
+ assertFalse(sub.isClosedForReceive)
+ for (x in 1..3)
+ assertEquals(x, sub.receiveOrNull())
+ // and receive close signal
+ assertNull(sub.receiveOrNull())
+ assertTrue(sub.isClosedForReceive)
+ finish(7)
+ }
+
+ @Test
+ fun testForgetUnsubscribed() = runTest {
+ expect(1)
+ val broadcast = BroadcastChannel<Int>(1)
+ broadcast.send(1)
+ broadcast.send(2)
+ broadcast.send(3)
+ expect(2) // should not suspend anywhere above
+ val sub = broadcast.openSubscription()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(3)
+ assertEquals(4, sub.receive()) // suspends
+ expect(5)
+ }
+ expect(4)
+ broadcast.send(4) // sends
+ yield()
+ finish(6)
+ }
+
+ @Test
+ fun testReceiveFullAfterClose() = runTest {
+ val channel = BroadcastChannel<Int>(10)
+ val sub = channel.openSubscription()
+ // generate into buffer & close
+ for (x in 1..5) channel.send(x)
+ channel.close()
+ // 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.isClosedForReceive)
+ }
+
+ @Test
+ fun testCloseSubDuringIteration() = runTest {
+ val channel = BroadcastChannel<Int>(1)
+ // launch generator (for later) in this context
+ launch {
+ for (x in 1..5) {
+ channel.send(x)
+ }
+ channel.close()
+ }
+ // start consuming
+ val sub = channel.openSubscription()
+ var expected = 0
+ assertFailsWith<CancellationException> {
+ sub.consumeEach {
+ check(it == ++expected)
+ if (it == 2) {
+ sub.cancel()
+ }
+ }
+ }
+ check(expected == 2)
+ }
+
+ @Test
+ fun testReceiveFromCancelledSub() = runTest {
+ val channel = BroadcastChannel<Int>(1)
+ val sub = channel.openSubscription()
+ assertFalse(sub.isClosedForReceive)
+ sub.cancel()
+ assertTrue(sub.isClosedForReceive)
+ assertFailsWith<CancellationException> { sub.receive() }
+ }
+
+ @Test
+ fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
+ val channel = BroadcastChannel<Int>(1)
+ val subscription = channel.openSubscription()
+ subscription.cancel(TestCancellationException())
+ subscription.receiveOrNull()
+ }
+
+ @Test
+ fun testReceiveNoneAfterCancel() = runTest {
+ val channel = BroadcastChannel<Int>(10)
+ val sub = channel.openSubscription()
+ // generate into buffer & cancel
+ for (x in 1..5) channel.send(x)
+ channel.cancel()
+ assertTrue(channel.isClosedForSend)
+ assertTrue(sub.isClosedForReceive)
+ check(sub.receiveOrNull() == null)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt
new file mode 100644
index 00000000..ceef21ed
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2016-2018 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 ArrayChannelTest : TestBase() {
+ @Test
+ fun testSimple() = runTest {
+ val q = Channel<Int>(1)
+ check(q.isEmpty)
+ expect(1)
+ val sender = launch {
+ expect(4)
+ q.send(1) // success -- buffered
+ check(!q.isEmpty)
+ expect(5)
+ q.send(2) // suspends (buffer full)
+ expect(9)
+ }
+ expect(2)
+ val receiver = launch {
+ expect(6)
+ check(q.receive() == 1) // does not suspend -- took from buffer
+ check(!q.isEmpty) // waiting sender's element moved to buffer
+ expect(7)
+ check(q.receive() == 2) // does not suspend (takes from sender)
+ expect(8)
+ }
+ expect(3)
+ sender.join()
+ receiver.join()
+ check(q.isEmpty)
+ finish(10)
+ }
+
+ @Test
+ fun testClosedBufferedReceiveOrNull() = 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())
+ expect(6)
+ check(!q.isEmpty && q.isClosedForSend && q.isClosedForReceive)
+ assertEquals(null, q.receiveOrNull())
+ expect(7)
+ }
+ expect(2)
+ q.send(42) // buffers
+ expect(3)
+ q.close() // goes on
+ expect(4)
+ check(!q.isEmpty && q.isClosedForSend && !q.isClosedForReceive)
+ yield()
+ check(!q.isEmpty && q.isClosedForSend && q.isClosedForReceive)
+ finish(8)
+ }
+
+ @Test
+ fun testClosedExceptions() = runTest {
+ val q = Channel<Int>(1)
+ expect(1)
+ launch {
+ expect(4)
+ try { q.receive() }
+ catch (e: ClosedReceiveChannelException) {
+ expect(5)
+ }
+ }
+ expect(2)
+
+ require(q.close())
+ expect(3)
+ yield()
+ expect(6)
+ try { q.send(42) }
+ catch (e: ClosedSendChannelException) {
+ finish(7)
+ }
+ }
+
+ @Test
+ fun testOfferAndPoll() = runTest {
+ val q = Channel<Int>(1)
+ assertTrue(q.offer(1))
+ expect(1)
+ launch {
+ expect(3)
+ assertEquals(1, q.poll())
+ expect(4)
+ assertEquals(null, q.poll())
+ expect(5)
+ assertEquals(2, q.receive()) // suspends
+ expect(9)
+ assertEquals(3, q.poll())
+ expect(10)
+ assertEquals(null, q.poll())
+ expect(11)
+ }
+ expect(2)
+ yield()
+ expect(6)
+ assertTrue(q.offer(2))
+ expect(7)
+ assertTrue(q.offer(3))
+ expect(8)
+ assertFalse(q.offer(4))
+ yield()
+ finish(12)
+ }
+
+ @Test
+ fun testConsumeAll() = runTest {
+ val q = Channel<Int>(5)
+ for (i in 1..10) {
+ if (i <= 5) {
+ expect(i)
+ q.send(i) // shall buffer
+ } else {
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(i)
+ q.send(i) // suspends
+ expectUnreached() // will get cancelled by cancel
+ }
+ }
+ }
+ expect(11)
+ q.cancel()
+ check(q.isClosedForSend)
+ check(q.isClosedForReceive)
+ assertFailsWith<CancellationException> { q.receiveOrNull() }
+ finish(12)
+ }
+
+ @Test
+ fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
+ val channel = Channel<Int>(5)
+ channel.cancel(TestCancellationException())
+ channel.receiveOrNull()
+ }
+
+ @Test
+ fun testBufferSize() = runTest {
+ val capacity = 42
+ val channel = Channel<Int>(capacity)
+ checkBufferChannel(channel, capacity)
+ }
+
+ @Test
+ fun testBufferSizeFromTheMiddle() = runTest {
+ val capacity = 42
+ val channel = Channel<Int>(capacity)
+ repeat(4) {
+ channel.offer(-1)
+ }
+ repeat(4) {
+ channel.receiveOrNull()
+ }
+ checkBufferChannel(channel, capacity)
+ }
+
+ private suspend fun CoroutineScope.checkBufferChannel(
+ channel: Channel<Int>,
+ capacity: Int
+ ) {
+ launch {
+ expect(2)
+ repeat(42) {
+ channel.send(it)
+ }
+ expect(3)
+ channel.send(42)
+ expect(5)
+ channel.close()
+ }
+
+ expect(1)
+ yield()
+
+ expect(4)
+ val result = ArrayList<Int>(42)
+ channel.consumeEach {
+ result.add(it)
+ }
+ assertEquals((0..capacity).toList(), result)
+ finish(6)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
new file mode 100644
index 00000000..a6ddd811
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2016-2018 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 BasicOperationsTest : TestBase() {
+ @Test
+ fun testSimpleSendReceive() = runTest {
+ // Parametrized common test :(
+ TestChannelKind.values().forEach { kind -> testSendReceive(kind, 20) }
+ }
+
+ @Test
+ fun testOfferAfterClose() = runTest {
+ TestChannelKind.values().forEach { kind -> testOffer(kind) }
+ }
+
+ @Test
+ fun testSendAfterClose() = runTest {
+ TestChannelKind.values().forEach { kind -> testSendAfterClose(kind) }
+ }
+
+ @Test
+ fun testReceiveOrNullAfterClose() = runTest {
+ TestChannelKind.values().forEach { kind -> testReceiveOrNull(kind) }
+ }
+
+ @Test
+ fun testReceiveOrNullAfterCloseWithException() = runTest {
+ TestChannelKind.values().forEach { kind -> testReceiveOrNullException(kind) }
+ }
+
+ @Test
+ fun testReceiveOrClosed() = runTest {
+ TestChannelKind.values().forEach { kind -> testReceiveOrClosed(kind) }
+ }
+
+ @Test
+ fun testInvokeOnClose() = TestChannelKind.values().forEach { kind ->
+ reset()
+ val channel = kind.create()
+ channel.invokeOnClose {
+ if (it is AssertionError) {
+ expect(3)
+ }
+ }
+ expect(1)
+ channel.offer(42)
+ expect(2)
+ channel.close(AssertionError())
+ finish(4)
+ }
+
+ @Test
+ fun testInvokeOnClosed() = TestChannelKind.values().forEach { kind ->
+ reset()
+ expect(1)
+ val channel = kind.create()
+ channel.close()
+ channel.invokeOnClose { expect(2) }
+ assertFailsWith<IllegalStateException> { channel.invokeOnClose { expect(3) } }
+ finish(3)
+ }
+
+ @Test
+ fun testMultipleInvokeOnClose() = TestChannelKind.values().forEach { kind ->
+ reset()
+ val channel = kind.create()
+ channel.invokeOnClose { expect(3) }
+ expect(1)
+ assertFailsWith<IllegalStateException> { channel.invokeOnClose { expect(4) } }
+ expect(2)
+ channel.close()
+ finish(4)
+ }
+
+ @Test
+ fun testIterator() = runTest {
+ TestChannelKind.values().forEach { kind ->
+ val channel = kind.create()
+ val iterator = channel.iterator()
+ assertFailsWith<IllegalStateException> { iterator.next() }
+ channel.close()
+ assertFailsWith<IllegalStateException> { iterator.next() }
+ assertFalse(iterator.hasNext())
+ }
+ }
+
+ private suspend fun testReceiveOrNull(kind: TestChannelKind) = coroutineScope {
+ val channel = kind.create()
+ 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()
+ 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 {
+ reset()
+ val channel = kind.create()
+ launch {
+ expect(2)
+ channel.send(1)
+ }
+
+ expect(1)
+ val result = channel.receiveOrClosed()
+ assertEquals(1, result.value)
+ assertEquals(1, result.valueOrNull)
+ assertTrue(ValueOrClosed.value(1) == result)
+
+ expect(3)
+ launch {
+ expect(4)
+ channel.close()
+ }
+ val closed = channel.receiveOrClosed()
+ expect(5)
+ assertNull(closed.valueOrNull)
+ assertTrue(closed.isClosed)
+ assertNull(closed.closeCause)
+ assertTrue(ValueOrClosed.closed<Int>(closed.closeCause) == closed)
+ finish(6)
+ }
+
+ private suspend fun testOffer(kind: TestChannelKind) = coroutineScope {
+ val channel = kind.create()
+ 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())
+ }
+ }
+
+ d.await()
+ }
+
+ /**
+ * [ClosedSendChannelException] should not be eaten.
+ * See [https://github.com/Kotlin/kotlinx.coroutines/issues/957]
+ */
+ private suspend fun testSendAfterClose(kind: TestChannelKind) {
+ assertFailsWith<ClosedSendChannelException> {
+ coroutineScope {
+ val channel = kind.create()
+ channel.close()
+
+ launch {
+ channel.send(1)
+ }
+ }
+ }
+ }
+
+ private suspend fun testSendReceive(kind: TestChannelKind, iterations: Int) = coroutineScope {
+ val channel = kind.create()
+ launch {
+ repeat(iterations) { channel.send(it) }
+ channel.close()
+ }
+ var expected = 0
+ for (x in channel) {
+ if (!kind.isConflated) {
+ assertEquals(expected++, x)
+ } else {
+ assertTrue(x >= expected)
+ expected = x + 1
+ }
+ }
+ if (!kind.isConflated) {
+ assertEquals(iterations, expected)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt b/kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt
new file mode 100644
index 00000000..61e93fa8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2018 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 BroadcastChannelFactoryTest : TestBase() {
+
+ @Test
+ fun testRendezvousChannelNotSupported() {
+ assertFailsWith<IllegalArgumentException> { BroadcastChannel<Int>(0) }
+ }
+
+ @Test
+ fun testLinkedListChannelNotSupported() {
+ assertFailsWith<IllegalArgumentException> { BroadcastChannel<Int>(Channel.UNLIMITED) }
+ }
+
+ @Test
+ fun testConflatedBroadcastChannel() {
+ assertTrue { BroadcastChannel<Int>(Channel.CONFLATED) is ConflatedBroadcastChannel }
+ }
+
+ @Test
+ fun testArrayBroadcastChannel() {
+ assertTrue { BroadcastChannel<Int>(1) is ArrayBroadcastChannel }
+ assertTrue { BroadcastChannel<Int>(10) is ArrayBroadcastChannel }
+ }
+
+ @Test
+ fun testInvalidCapacityNotSupported() {
+ assertFailsWith<IllegalArgumentException> { BroadcastChannel<Int>(-3) }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt b/kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt
new file mode 100644
index 00000000..878437bb
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2018 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.coroutines.*
+import kotlin.test.*
+
+class BroadcastTest : TestBase() {
+ @Test
+ fun testBroadcastBasic() = runTest {
+ expect(1)
+ val b = broadcast {
+ expect(4)
+ send(1) // goes to receiver
+ expect(5)
+ send(2) // goes to buffer
+ expect(6)
+ send(3) // suspends, will not be consumes, but will not be cancelled either
+ expect(10)
+ }
+ yield() // has no effect, because default is lazy
+ expect(2)
+ b.consume {
+ expect(3)
+ assertEquals(1, receive()) // suspends
+ expect(7)
+ assertEquals(2, receive()) // suspends
+ expect(8)
+ }
+ expect(9)
+ yield() // to broadcast
+ finish(11)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt
new file mode 100644
index 00000000..72ba3154
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2018 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 ChannelFactoryTest : TestBase() {
+
+ @Test
+ fun testRendezvousChannel() {
+ assertTrue(Channel<Int>() is RendezvousChannel)
+ assertTrue(Channel<Int>(0) is RendezvousChannel)
+ }
+
+ @Test
+ fun testLinkedListChannel() {
+ assertTrue(Channel<Int>(Channel.UNLIMITED) is LinkedListChannel)
+ }
+
+ @Test
+ fun testConflatedChannel() {
+ assertTrue(Channel<Int>(Channel.CONFLATED) is ConflatedChannel)
+ }
+
+ @Test
+ fun testArrayChannel() {
+ assertTrue(Channel<Int>(1) is ArrayChannel)
+ assertTrue(Channel<Int>(10) is ArrayChannel)
+ }
+
+ @Test
+ fun testInvalidCapacityNotSupported() = runTest({ it is IllegalArgumentException }) {
+ Channel<Int>(-3)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelReceiveOrClosedTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelReceiveOrClosedTest.kt
new file mode 100644
index 00000000..e58b0dee
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ChannelReceiveOrClosedTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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/ChannelsTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt
new file mode 100644
index 00000000..983f353f
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt
@@ -0,0 +1,562 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.math.*
+import kotlin.test.*
+
+class ChannelsTest: TestBase() {
+ private val testList = listOf(1, 2, 3)
+
+ @Test
+ fun testIterableAsReceiveChannel() = runTest {
+ assertEquals(testList, testList.asReceiveChannel().toList())
+ }
+
+ @Test
+ fun testAssociate() = runTest {
+ assertEquals(testList.associate { it * 2 to it * 3 },
+ testList.asReceiveChannel().associate { it * 2 to it * 3 }.toMap())
+ }
+
+ @Test
+ fun testAssociateBy() = runTest {
+ assertEquals(testList.associateBy { it % 2 }, testList.asReceiveChannel().associateBy { it % 2 })
+ }
+
+ @Test
+ fun testAssociateBy2() = runTest {
+ assertEquals(testList.associateBy({ it * 2}, { it * 3 }),
+ testList.asReceiveChannel().associateBy({ it * 2}, { it * 3 }).toMap())
+ }
+
+ @Test
+ fun testDistinct() = runTest {
+ assertEquals(testList.map { it % 2 }.distinct(), testList.asReceiveChannel().map { it % 2 }.distinct().toList())
+ }
+
+ @Test
+ fun testDistinctBy() = runTest {
+ assertEquals(testList.distinctBy { it % 2 }.toList(), testList.asReceiveChannel().distinctBy { it % 2 }.toList())
+ }
+
+ @Test
+ fun testToCollection() = runTest {
+ val target = mutableListOf<Int>()
+ testList.asReceiveChannel().toCollection(target)
+ assertEquals(testList, target)
+ }
+
+ @Test
+ fun testDrop() = runTest {
+ for (i in 0..testList.size) {
+ assertEquals(testList.drop(i), testList.asReceiveChannel().drop(i).toList(), "Drop $i")
+ }
+ }
+
+ @Test
+ fun testElementAtOrElse() = runTest {
+ assertEquals(testList.elementAtOrElse(2) { 42 }, testList.asReceiveChannel().elementAtOrElse(2) { 42 })
+ assertEquals(testList.elementAtOrElse(9) { 42 }, testList.asReceiveChannel().elementAtOrElse(9) { 42 })
+ }
+
+ @Test
+ fun testFirst() = runTest {
+ assertEquals(testList.first(), testList.asReceiveChannel().first())
+ for (i in testList) {
+ assertEquals(testList.first { it == i }, testList.asReceiveChannel().first { it == i })
+ }
+ try {
+ testList.asReceiveChannel().first { it == 9 }
+ fail()
+ } catch (nse: NoSuchElementException) {
+ }
+ }
+
+ @Test
+ fun testFirstOrNull() = runTest {
+ assertEquals(testList.firstOrNull(), testList.asReceiveChannel().firstOrNull())
+ assertEquals(testList.firstOrNull { it == 2 }, testList.asReceiveChannel().firstOrNull { it == 2 })
+ assertEquals(testList.firstOrNull { it == 9 }, testList.asReceiveChannel().firstOrNull { it == 9 })
+ }
+
+ @Test
+ fun testFlatMap() = runTest {
+ assertEquals(testList.flatMap { (0..it).toList() }, testList.asReceiveChannel().flatMap { (0..it).asReceiveChannel() }.toList())
+
+ }
+
+ @Test
+ fun testFold() = runTest {
+ assertEquals(testList.fold(mutableListOf(42)) { acc, e -> acc.apply { add(e) } },
+ testList.asReceiveChannel().fold(mutableListOf(42)) { acc, e -> acc.apply { add(e) } }.toList())
+ }
+
+ @Test
+ fun testFoldIndexed() = runTest {
+ assertEquals(testList.foldIndexed(mutableListOf(42)) { index, acc, e -> acc.apply { add(index + e) } },
+ testList.asReceiveChannel().foldIndexed(mutableListOf(42)) { index, acc, e -> acc.apply { add(index + e) } }.toList())
+ }
+
+ @Test
+ fun testGroupBy() = runTest {
+ assertEquals(testList.groupBy { it % 2 }, testList.asReceiveChannel().groupBy { it % 2 })
+ }
+
+ @Test
+ fun testGroupBy2() = runTest {
+ assertEquals(testList.groupBy({ -it }, { it + 100 }), testList.asReceiveChannel().groupBy({ -it }, { it + 100 }).toMap())
+
+ }
+
+ @Test
+ fun testMap() = runTest {
+ assertEquals(testList.map { it + 10 }, testList.asReceiveChannel().map { it + 10 }.toList())
+
+ }
+
+ @Test
+ fun testMapToCollection() = runTest {
+ val c = mutableListOf<Int>()
+ testList.asReceiveChannel().mapTo(c) { it + 10 }
+ assertEquals(testList.map { it + 10 }, c)
+ }
+
+ @Test
+ fun testMapToSendChannel() = runTest {
+ val c = produce<Int> {
+ testList.asReceiveChannel().mapTo(channel) { it + 10 }
+ }
+ assertEquals(testList.map { it + 10 }, c.toList())
+ }
+
+ @Test
+ fun testEmptyList() = runTest {
+ assertTrue(emptyList<Nothing>().asReceiveChannel().toList().isEmpty())
+ }
+
+ @Test
+ fun testToList() = runTest {
+ assertEquals(testList, testList.asReceiveChannel().toList())
+
+ }
+
+ @Test
+ fun testEmptySet() = runTest {
+ assertTrue(emptyList<Nothing>().asReceiveChannel().toSet().isEmpty())
+
+ }
+
+ @Test
+ fun testToSet() = runTest {
+ assertEquals(testList.toSet(), testList.asReceiveChannel().toSet())
+ }
+
+ @Test
+ fun testToMutableSet() = runTest {
+ assertEquals(testList.toMutableSet(), testList.asReceiveChannel().toMutableSet())
+ }
+
+ @Test
+ fun testEmptySequence() = runTest {
+ val channel = Channel<Nothing>()
+ channel.close()
+
+ assertEquals(emptyList<Nothing>().asReceiveChannel().count(), 0)
+ }
+
+ @Test
+ fun testEmptyMap() = runTest {
+ val channel = Channel<Pair<Nothing, Nothing>>()
+ channel.close()
+
+ assertTrue(channel.toMap().isEmpty())
+ }
+
+ @Test
+ fun testToMap() = runTest {
+ val values = testList.map { it to it.toString() }
+ assertEquals(values.toMap(), values.asReceiveChannel().toMap())
+ }
+
+ @Test
+ fun testReduce() = runTest {
+ assertEquals(testList.reduce { acc, e -> acc * e },
+ testList.asReceiveChannel().reduce { acc, e -> acc * e })
+ }
+
+ @Test
+ fun testReduceIndexed() = runTest {
+ assertEquals(testList.reduceIndexed { index, acc, e -> index + acc * e },
+ testList.asReceiveChannel().reduceIndexed { index, acc, e -> index + acc * e })
+ }
+
+ @Test
+ fun testTake() = runTest {
+ for (i in 0..testList.size) {
+ assertEquals(testList.take(i), testList.asReceiveChannel().take(i).toList())
+ }
+ }
+
+ @Test
+ fun testPartition() = runTest {
+ assertEquals(testList.partition { it % 2 == 0 }, testList.asReceiveChannel().partition { it % 2 == 0 })
+ }
+
+ @Test
+ fun testZip() = runTest {
+ val other = listOf("a", "b")
+ assertEquals(testList.zip(other), testList.asReceiveChannel().zip(other.asReceiveChannel()).toList())
+ }
+
+ @Test
+ fun testElementAt() = runTest {
+ testList.indices.forEach { i ->
+ assertEquals(testList[i], testList.asReceiveChannel().elementAt(i))
+ }
+ }
+
+ @Test
+ fun testElementAtOrNull() = runTest {
+ testList.indices.forEach { i ->
+ assertEquals(testList[i], testList.asReceiveChannel().elementAtOrNull(i))
+ }
+ assertEquals(null, testList.asReceiveChannel().elementAtOrNull(-1))
+ assertEquals(null, testList.asReceiveChannel().elementAtOrNull(testList.size))
+ }
+
+ @Test
+ fun testFind() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.find { it % 2 == mod },
+ testList.asReceiveChannel().find { it % 2 == mod })
+ }
+ }
+
+ @Test
+ fun testFindLast() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.findLast { it % 2 == mod }, testList.asReceiveChannel().findLast { it % 2 == mod })
+ }
+ }
+
+ @Test
+ fun testIndexOf() = runTest {
+ repeat(testList.size + 1) { i ->
+ assertEquals(testList.indexOf(i), testList.asReceiveChannel().indexOf(i))
+ }
+ }
+
+ @Test
+ fun testLastIndexOf() = runTest {
+ repeat(testList.size + 1) { i ->
+ assertEquals(testList.lastIndexOf(i), testList.asReceiveChannel().lastIndexOf(i))
+ }
+ }
+
+ @Test
+ fun testIndexOfFirst() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.indexOfFirst { it % 2 == mod },
+ testList.asReceiveChannel().indexOfFirst { it % 2 == mod })
+ }
+ }
+
+ @Test
+ fun testIndexOfLast() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.indexOfLast { it % 2 != mod },
+ testList.asReceiveChannel().indexOfLast { it % 2 != mod })
+ }
+ }
+
+ @Test
+ fun testLastOrNull() = runTest {
+ assertEquals(testList.lastOrNull(), testList.asReceiveChannel().lastOrNull())
+ assertEquals(null, emptyList<Int>().asReceiveChannel().lastOrNull())
+ }
+
+ @Test
+ fun testSingleOrNull() = runTest {
+ assertEquals(1, listOf(1).asReceiveChannel().singleOrNull())
+ assertEquals(null, listOf(1, 2).asReceiveChannel().singleOrNull())
+ assertEquals(null, emptyList<Int>().asReceiveChannel().singleOrNull())
+ repeat(testList.size + 1) { i ->
+ assertEquals(testList.singleOrNull { it == i },
+ testList.asReceiveChannel().singleOrNull { it == i })
+ }
+ repeat(3) { mod ->
+ assertEquals(testList.singleOrNull { it % 2 == mod },
+ testList.asReceiveChannel().singleOrNull { it % 2 == mod })
+ }
+ }
+
+ @Test
+ fun testDropWhile() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.dropWhile { it % 2 == mod },
+ testList.asReceiveChannel().dropWhile { it % 2 == mod }.toList())
+ }
+ }
+
+ @Test
+ fun testFilter() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.filter { it % 2 == mod },
+ testList.asReceiveChannel().filter { it % 2 == mod }.toList())
+ }
+ }
+
+ @Test
+ fun testFilterToCollection() = runTest {
+ repeat(3) { mod ->
+ val c = mutableListOf<Int>()
+ testList.asReceiveChannel().filterTo(c) { it % 2 == mod }
+ assertEquals(testList.filter { it % 2 == mod }, c)
+ }
+ }
+
+ @Test
+ fun testFilterToSendChannel() = runTest {
+ repeat(3) { mod ->
+ val c = produce<Int> {
+ testList.asReceiveChannel().filterTo(channel) { it % 2 == mod }
+ }
+ assertEquals(testList.filter { it % 2 == mod }, c.toList())
+ }
+ }
+
+ @Test
+ fun testFilterNot() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.filterNot { it % 2 == mod },
+ testList.asReceiveChannel().filterNot { it % 2 == mod }.toList())
+ }
+ }
+
+ @Test
+ fun testFilterNotToCollection() = runTest {
+ repeat(3) { mod ->
+ val c = mutableListOf<Int>()
+ testList.asReceiveChannel().filterNotTo(c) { it % 2 == mod }
+ assertEquals(testList.filterNot { it % 2 == mod }, c)
+ }
+ }
+
+ @Test
+ fun testFilterNotToSendChannel() = runTest {
+ repeat(3) { mod ->
+ val c = produce<Int> {
+ testList.asReceiveChannel().filterNotTo(channel) { it % 2 == mod }
+ }
+ assertEquals(testList.filterNot { it % 2 == mod }, c.toList())
+ }
+ }
+
+ @Test
+ fun testFilterNotNull() = runTest {
+ repeat(3) { mod ->
+ assertEquals(
+ testList.mapNotNull { it.takeIf { it % 2 == mod } },
+ testList.asReceiveChannel().map { it.takeIf { it % 2 == mod } }.filterNotNull().toList())
+ }
+ }
+
+ @Test
+ fun testFilterNotNullToCollection() = runTest {
+ repeat(3) { mod ->
+ val c = mutableListOf<Int>()
+ testList.asReceiveChannel().map { it.takeIf { it % 2 == mod } }.filterNotNullTo(c)
+ assertEquals(testList.mapNotNull { it.takeIf { it % 2 == mod } }, c)
+ }
+ }
+
+ @Test
+ fun testFilterNotNullToSendChannel() = runTest {
+ repeat(3) { mod ->
+ val c = produce<Int> {
+ testList.asReceiveChannel().map { it.takeIf { it % 2 == mod } }.filterNotNullTo(channel)
+ }
+ assertEquals(testList.mapNotNull { it.takeIf { it % 2 == mod } }, c.toList())
+ }
+ }
+
+ @Test
+ fun testFilterIndexed() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.filterIndexed { index, _ -> index % 2 == mod },
+ testList.asReceiveChannel().filterIndexed { index, _ -> index % 2 == mod }.toList())
+ }
+ }
+
+ @Test
+ fun testFilterIndexedToCollection() = runTest {
+ repeat(3) { mod ->
+ val c = mutableListOf<Int>()
+ testList.asReceiveChannel().filterIndexedTo(c) { index, _ -> index % 2 == mod }
+ assertEquals(testList.filterIndexed { index, _ -> index % 2 == mod }, c)
+ }
+ }
+
+ @Test
+ fun testFilterIndexedToChannel() = runTest {
+ repeat(3) { mod ->
+ val c = produce<Int> {
+ testList.asReceiveChannel().filterIndexedTo(channel) { index, _ -> index % 2 == mod }
+ }
+ assertEquals(testList.filterIndexed { index, _ -> index % 2 == mod }, c.toList())
+ }
+ }
+
+ @Test
+ fun testTakeWhile() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.takeWhile { it % 2 != mod },
+ testList.asReceiveChannel().takeWhile { it % 2 != mod }.toList())
+ }
+ }
+
+ @Test
+ fun testToChannel() = runTest {
+ val c = produce<Int> {
+ testList.asReceiveChannel().toChannel(channel)
+ }
+ assertEquals(testList, c.toList())
+ }
+
+ @Test
+ fun testMapIndexed() = runTest {
+ assertEquals(testList.mapIndexed { index, i -> index + i },
+ testList.asReceiveChannel().mapIndexed { index, i -> index + i }.toList())
+ }
+
+ @Test
+ fun testMapIndexedToCollection() = runTest {
+ val c = mutableListOf<Int>()
+ testList.asReceiveChannel().mapIndexedTo(c) { index, i -> index + i }
+ assertEquals(testList.mapIndexed { index, i -> index + i }, c)
+ }
+
+ @Test
+ fun testMapIndexedToSendChannel() = runTest {
+ val c = produce<Int> {
+ testList.asReceiveChannel().mapIndexedTo(channel) { index, i -> index + i }
+ }
+ assertEquals(testList.mapIndexed { index, i -> index + i }, c.toList())
+ }
+
+ @Test
+ fun testMapNotNull() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.mapNotNull { i -> i.takeIf { i % 2 == mod } },
+ testList.asReceiveChannel().mapNotNull { i -> i.takeIf { i % 2 == mod } }.toList())
+ }
+ }
+
+ @Test
+ fun testMapNotNullToCollection() = runTest {
+ repeat(3) { mod ->
+ val c = mutableListOf<Int>()
+ testList.asReceiveChannel().mapNotNullTo(c) { i -> i.takeIf { i % 2 == mod } }
+ assertEquals(testList.mapNotNull { i -> i.takeIf { i % 2 == mod } }, c)
+ }
+ }
+
+ @Test
+ fun testMapNotNullToSendChannel() = runTest {
+ repeat(3) { mod ->
+ val c = produce<Int> {
+ testList.asReceiveChannel().mapNotNullTo(channel) { i -> i.takeIf { i % 2 == mod } }
+ }
+ assertEquals(testList.mapNotNull { i -> i.takeIf { i % 2 == mod } }, c.toList())
+ }
+ }
+
+ @Test
+ fun testMapIndexedNotNull() = runTest {
+ repeat(3) { mod ->
+ assertEquals(testList.mapIndexedNotNull { index, i -> index.takeIf { i % 2 == mod } },
+ testList.asReceiveChannel().mapIndexedNotNull { index, i -> index.takeIf { i % 2 == mod } }.toList())
+ }
+ }
+
+ @Test
+ fun testMapIndexedNotNullToCollection() = runTest {
+ repeat(3) { mod ->
+ val c = mutableListOf<Int>()
+ testList.asReceiveChannel().mapIndexedNotNullTo(c) { index, i -> index.takeIf { i % 2 == mod } }
+ assertEquals(testList.mapIndexedNotNull { index, i -> index.takeIf { i % 2 == mod } }, c)
+ }
+ }
+
+ @Test
+ fun testMapIndexedNotNullToSendChannel() = runTest {
+ repeat(3) { mod ->
+ val c = produce<Int> {
+ testList.asReceiveChannel().mapIndexedNotNullTo(channel) { index, i -> index.takeIf { i % 2 == mod } }
+ }
+ assertEquals(testList.mapIndexedNotNull { index, i -> index.takeIf { i % 2 == mod } }, c.toList())
+ }
+ }
+
+ @Test
+ fun testWithIndex() = runTest {
+ assertEquals(testList.withIndex().toList(), testList.asReceiveChannel().withIndex().toList())
+ }
+
+ @Test
+ fun testMaxBy() = runTest {
+ assertEquals(testList.maxBy { 10 - abs(it - 2) },
+ testList.asReceiveChannel().maxBy { 10 - abs(it - 2) })
+ }
+
+ @Test
+ fun testMaxWith() = runTest {
+ val cmp = compareBy<Int> { 10 - abs(it - 2) }
+ assertEquals(testList.maxWith(cmp),
+ testList.asReceiveChannel().maxWith(cmp))
+ }
+
+ @Test
+ fun testMinBy() = runTest {
+ assertEquals(testList.minBy { abs(it - 2) },
+ testList.asReceiveChannel().minBy { abs(it - 2) })
+ }
+
+ @Test
+ fun testMinWith() = runTest {
+ val cmp = compareBy<Int> { abs(it - 2) }
+ assertEquals(testList.minWith(cmp),
+ testList.asReceiveChannel().minWith(cmp))
+ }
+
+ @Test
+ fun testSumBy() = runTest {
+ assertEquals(testList.sumBy { it * 3 },
+ testList.asReceiveChannel().sumBy { it * 3 })
+ }
+
+ @Test
+ fun testSumByDouble() = runTest {
+ val expected = testList.sumByDouble { it * 3.0 }
+ val actual = testList.asReceiveChannel().sumByDouble { it * 3.0 }
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun testRequireNoNulls() = runTest {
+ assertEquals(testList.requireNoNulls(), testList.asReceiveChannel().requireNoNulls().toList())
+ }
+
+ private fun <E> Iterable<E>.asReceiveChannel(context: CoroutineContext = Dispatchers.Unconfined): ReceiveChannel<E> =
+ GlobalScope.produce(context) {
+ for (element in this@asReceiveChannel)
+ send(element)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt
new file mode 100644
index 00000000..7dd232f2
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016-2018 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 ConflatedBroadcastChannelTest : TestBase() {
+
+ @Test
+ fun testConcurrentModification() = runTest {
+ val channel = ConflatedBroadcastChannel<Int>()
+ val s1 = channel.openSubscription()
+ val s2 = channel.openSubscription()
+
+ val job1 = launch(Dispatchers.Unconfined, CoroutineStart.UNDISPATCHED) {
+ expect(1)
+ s1.receive()
+ s1.cancel()
+ }
+
+ val job2 = launch(Dispatchers.Unconfined, CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ s2.receive()
+ }
+
+ expect(3)
+ channel.send(1)
+ joinAll(job1, job2)
+ finish(4)
+ }
+
+ @Test
+ fun testBasicScenario() = runTest {
+ expect(1)
+ val broadcast = ConflatedBroadcastChannel<String>()
+ assertTrue(exceptionFrom { broadcast.value } is IllegalStateException)
+ assertNull(broadcast.valueOrNull)
+
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ val sub = broadcast.openSubscription()
+ assertNull(sub.poll())
+ expect(3)
+ assertEquals("one", sub.receive()) // suspends
+ expect(6)
+ assertEquals("two", sub.receive()) // suspends
+ expect(12)
+ sub.cancel()
+ expect(13)
+ }
+
+ expect(4)
+ broadcast.send("one") // does not suspend
+ assertEquals("one", broadcast.value)
+ assertEquals("one", broadcast.valueOrNull)
+ expect(5)
+ yield() // to receiver
+ expect(7)
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(8)
+ val sub = broadcast.openSubscription()
+ assertEquals("one", sub.receive()) // does not suspend
+ expect(9)
+ assertEquals("two", sub.receive()) // suspends
+ expect(14)
+ assertEquals("three", sub.receive()) // suspends
+ expect(17)
+ assertNull(sub.receiveOrNull()) // suspends until closed
+ expect(20)
+ sub.cancel()
+ expect(21)
+ }
+
+ expect(10)
+ broadcast.send("two") // does not suspend
+ assertEquals("two", broadcast.value)
+ assertEquals("two", broadcast.valueOrNull)
+ expect(11)
+ yield() // to both receivers
+ expect(15)
+ broadcast.send("three") // does not suspend
+ assertEquals("three", broadcast.value)
+ assertEquals("three", broadcast.valueOrNull)
+ expect(16)
+ yield() // to second receiver
+ expect(18)
+ broadcast.close()
+ assertTrue(exceptionFrom { broadcast.value } is IllegalStateException)
+ assertNull(broadcast.valueOrNull)
+ expect(19)
+ yield() // to second receiver
+ assertTrue(exceptionFrom { broadcast.send("four") } is ClosedSendChannelException)
+ finish(22)
+ }
+
+ @Test
+ fun testInitialValueAndReceiveClosed() = runTest {
+ expect(1)
+ val broadcast = ConflatedBroadcastChannel(1)
+ assertEquals(1, broadcast.value)
+ assertEquals(1, broadcast.valueOrNull)
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ val sub = broadcast.openSubscription()
+ assertEquals(1, sub.receive())
+ expect(3)
+ assertTrue(exceptionFrom { sub.receive() } is ClosedReceiveChannelException) // suspends
+ expect(6)
+ }
+ expect(4)
+ broadcast.close()
+ expect(5)
+ yield() // to child
+ finish(7)
+ }
+
+ private inline fun exceptionFrom(block: () -> Unit): Throwable? {
+ return try {
+ block()
+ null
+ } catch (e: Throwable) {
+ e
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt
new file mode 100644
index 00000000..4deb3858
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016-2018 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 ConflatedChannelTest : TestBase() {
+ @Test
+ fun testBasicConflationOfferPoll() {
+ val q = Channel<Int>(Channel.CONFLATED)
+ assertNull(q.poll())
+ assertTrue(q.offer(1))
+ assertTrue(q.offer(2))
+ assertTrue(q.offer(3))
+ assertEquals(3, q.poll())
+ assertNull(q.poll())
+ }
+
+ @Test
+ fun testConflatedSend() = runTest {
+ val q = ConflatedChannel<Int>()
+ q.send(1)
+ q.send(2) // shall conflated previously sent
+ assertEquals(2, q.receiveOrNull())
+ }
+
+ @Test
+ fun testConflatedClose() = runTest {
+ val q = Channel<Int>(Channel.CONFLATED)
+ q.send(1)
+ q.close() // shall become closed but do not conflate last sent item yet
+ assertTrue(q.isClosedForSend)
+ assertFalse(q.isClosedForReceive)
+ assertEquals(1, q.receive())
+ // not it is closed for receive, too
+ assertTrue(q.isClosedForSend)
+ assertTrue(q.isClosedForReceive)
+ assertNull(q.receiveOrNull())
+ }
+
+ @Test
+ fun testConflationSendReceive() = runTest {
+ val q = Channel<Int>(Channel.CONFLATED)
+ expect(1)
+ launch { // receiver coroutine
+ expect(4)
+ assertEquals(2, q.receive())
+ expect(5)
+ assertEquals(3, q.receive()) // this receive suspends
+ expect(8)
+ assertEquals(6, q.receive()) // last conflated value
+ expect(9)
+ }
+ expect(2)
+ q.send(1)
+ q.send(2) // shall conflate
+ expect(3)
+ yield() // to receiver
+ expect(6)
+ q.send(3) // send to the waiting receiver
+ q.send(4) // buffer
+ q.send(5) // conflate
+ q.send(6) // conflate again
+ expect(7)
+ yield() // to receiver
+ finish(10)
+ }
+
+ @Test
+ fun testConsumeAll() = runTest {
+ val q = Channel<Int>(Channel.CONFLATED)
+ expect(1)
+ for (i in 1..10) {
+ q.send(i) // stores as last
+ }
+ q.cancel()
+ check(q.isClosedForSend)
+ check(q.isClosedForReceive)
+ assertFailsWith<CancellationException> { q.receiveOrNull() }
+ finish(2)
+ }
+
+ @Test
+ fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
+ val channel = Channel<Int>(Channel.CONFLATED)
+ channel.cancel(TestCancellationException())
+ channel.receiveOrNull()
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt
new file mode 100644
index 00000000..4233a350
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-2018 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 LinkedListChannelTest : TestBase() {
+ @Test
+ fun testBasic() = runTest {
+ val c = Channel<Int>(Channel.UNLIMITED)
+ c.send(1)
+ check(c.offer(2))
+ c.send(3)
+ check(c.close())
+ check(!c.close())
+ assertEquals(1, c.receive())
+ assertEquals(2, c.poll())
+ assertEquals(3, c.receiveOrNull())
+ assertNull(c.receiveOrNull())
+ }
+
+ @Test
+ fun testConsumeAll() = runTest {
+ val q = Channel<Int>(Channel.UNLIMITED)
+ for (i in 1..10) {
+ q.send(i) // buffers
+ }
+ q.cancel()
+ check(q.isClosedForSend)
+ check(q.isClosedForReceive)
+ assertFailsWith<CancellationException> { q.receiveOrNull() }
+ }
+
+ @Test
+ fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
+ val channel = Channel<Int>(Channel.UNLIMITED)
+ channel.cancel(TestCancellationException())
+ channel.receiveOrNull()
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ProduceConsumeTest.kt b/kotlinx-coroutines-core/common/test/channels/ProduceConsumeTest.kt
new file mode 100644
index 00000000..5df0c7d6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ProduceConsumeTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016-2018 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.coroutines.*
+import kotlin.test.*
+
+class ProduceConsumeTest : TestBase() {
+
+ @Test
+ fun testRendezvous() = runTest {
+ testProducer(1)
+ }
+
+ @Test
+ fun testSmallBuffer() = runTest {
+ testProducer(1)
+ }
+
+ @Test
+ fun testMediumBuffer() = runTest {
+ testProducer(10)
+ }
+
+ @Test
+ fun testLargeMediumBuffer() = runTest {
+ testProducer(1000)
+ }
+
+ @Test
+ fun testUnlimited() = runTest {
+ testProducer(Channel.UNLIMITED)
+ }
+
+ private suspend fun testProducer(producerCapacity: Int) {
+ testProducer(1, producerCapacity)
+ testProducer(10, producerCapacity)
+ testProducer(100, producerCapacity)
+ }
+
+ private suspend fun testProducer(messages: Int, producerCapacity: Int) {
+ var sentAll = false
+ val producer = GlobalScope.produce(coroutineContext, capacity = producerCapacity) {
+ for (i in 1..messages) {
+ send(i)
+ }
+ sentAll = true
+ }
+ var consumed = 0
+ for (x in producer) {
+ consumed++
+ }
+ assertTrue(sentAll)
+ assertEquals(messages, consumed)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
new file mode 100644
index 00000000..bf85c74f
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2016-2018 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.coroutines.*
+import kotlin.test.*
+
+class ProduceTest : TestBase() {
+ @Test
+ fun testBasic() = runTest {
+ val c = produce {
+ expect(2)
+ send(1)
+ expect(3)
+ send(2)
+ expect(6)
+ }
+ expect(1)
+ check(c.receive() == 1)
+ expect(4)
+ check(c.receive() == 2)
+ expect(5)
+ check(c.receiveOrNull() == null)
+ finish(7)
+ }
+
+ @Test
+ fun testCancelWithoutCause() = runTest {
+ val c = produce(NonCancellable) {
+ expect(2)
+ send(1)
+ expect(3)
+ try {
+ send(2) // will get cancelled
+ expectUnreached()
+ } catch (e: Throwable) {
+ expect(7)
+ check(e is CancellationException)
+ throw e
+ }
+ expectUnreached()
+ }
+ expect(1)
+ check(c.receive() == 1)
+ expect(4)
+ c.cancel()
+ expect(5)
+ assertFailsWith<CancellationException> { c.receiveOrNull() }
+ expect(6)
+ yield() // to produce
+ finish(8)
+ }
+
+ @Test
+ fun testCancelWithCause() = runTest {
+ val c = produce(NonCancellable) {
+ expect(2)
+ send(1)
+ expect(3)
+ try {
+ send(2) // will get cancelled
+ expectUnreached()
+ } catch (e: Throwable) {
+ expect(6)
+ check(e is TestCancellationException)
+ throw e
+ }
+ expectUnreached()
+ }
+ expect(1)
+ check(c.receive() == 1)
+ expect(4)
+ c.cancel(TestCancellationException())
+ try {
+ assertNull(c.receiveOrNull())
+ expectUnreached()
+ } catch (e: TestCancellationException) {
+ expect(5)
+ }
+ yield() // to produce
+ finish(7)
+ }
+
+ @Test
+ fun testCancelOnCompletionUnconfined() = runTest {
+ cancelOnCompletion(Dispatchers.Unconfined)
+ }
+
+ @Test
+ fun testCancelOnCompletion() = runTest {
+ cancelOnCompletion(coroutineContext)
+ }
+
+ @Test
+ fun testAwaitConsumerCancellation() = runTest {
+ val parent = Job()
+ val channel = produce<Int>(parent) {
+ expect(2)
+ awaitClose { expect(4) }
+ }
+ expect(1)
+ yield()
+ expect(3)
+ channel.cancel()
+ parent.complete()
+ parent.join()
+ finish(5)
+ }
+
+ @Test
+ fun testAwaitProducerCancellation() = runTest {
+ val parent = Job()
+ produce<Int>(parent) {
+ expect(2)
+ launch {
+ expect(3)
+ this@produce.cancel()
+ }
+ awaitClose { expect(4) }
+ }
+ expect(1)
+ parent.complete()
+ parent.join()
+ finish(5)
+ }
+
+ @Test
+ fun testAwaitParentCancellation() = runTest {
+ val parent = Job()
+ produce<Int>(parent) {
+ expect(2)
+ awaitClose { expect(4) }
+ }
+ expect(1)
+ yield()
+ expect(3)
+ parent.cancelAndJoin()
+ finish(5)
+ }
+
+ @Test
+ fun testAwaitIllegalState() = runTest {
+ val channel = produce<Int> { }
+ @Suppress("RemoveExplicitTypeArguments") // KT-31525
+ assertFailsWith<IllegalStateException> { (channel as ProducerScope<*>).awaitClose() }
+ }
+
+ private suspend fun cancelOnCompletion(coroutineContext: CoroutineContext) = CoroutineScope(coroutineContext).apply {
+ val source = Channel<Int>()
+ expect(1)
+ val produced = produce<Int>(coroutineContext, onCompletion = source.consumes()) {
+ expect(2)
+ source.receive()
+ }
+
+ yield()
+ expect(3)
+ produced.cancel()
+ try {
+ source.receive()
+ } catch (e: CancellationException) {
+ finish(4)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt
new file mode 100644
index 00000000..54d69384
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2016-2018 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 RendezvousChannelTest : TestBase() {
+ @Test
+ fun testSimple() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ check(q.isEmpty)
+ expect(1)
+ val sender = launch {
+ expect(4)
+ q.send(1) // suspend -- the first to come to rendezvous
+ expect(7)
+ q.send(2) // does not suspend -- receiver is there
+ expect(8)
+ }
+ expect(2)
+ val receiver = launch {
+ expect(5)
+ check(q.receive() == 1) // does not suspend -- sender was there
+ expect(6)
+ check(q.receive() == 2) // suspends
+ expect(9)
+ }
+ expect(3)
+ sender.join()
+ receiver.join()
+ check(q.isEmpty)
+ finish(10)
+ }
+
+ @Test
+ fun testClosedReceiveOrNull() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ check(q.isEmpty && !q.isClosedForSend && !q.isClosedForReceive)
+ expect(1)
+ launch {
+ expect(3)
+ assertEquals(42, q.receiveOrNull())
+ expect(4)
+ assertEquals(null, q.receiveOrNull())
+ expect(6)
+ }
+ expect(2)
+ q.send(42)
+ expect(5)
+ q.close()
+ check(!q.isEmpty && q.isClosedForSend && q.isClosedForReceive)
+ yield()
+ check(!q.isEmpty && q.isClosedForSend && q.isClosedForReceive)
+ finish(7)
+ }
+
+ @Test
+ fun testClosedExceptions() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(4)
+ try { q.receive() }
+ catch (e: ClosedReceiveChannelException) {
+ expect(5)
+ }
+ }
+ expect(2)
+ q.close()
+ expect(3)
+ yield()
+ expect(6)
+ try { q.send(42) }
+ catch (e: ClosedSendChannelException) {
+ finish(7)
+ }
+ }
+
+ @Test
+ fun testOfferAndPool() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ assertFalse(q.offer(1))
+ expect(1)
+ launch {
+ expect(3)
+ assertEquals(null, q.poll())
+ expect(4)
+ assertEquals(2, q.receive())
+ expect(7)
+ assertEquals(null, q.poll())
+ yield()
+ expect(9)
+ assertEquals(3, q.poll())
+ expect(10)
+ }
+ expect(2)
+ yield()
+ expect(5)
+ assertTrue(q.offer(2))
+ expect(6)
+ yield()
+ expect(8)
+ q.send(3)
+ finish(11)
+ }
+
+ @Test
+ fun testIteratorClosed() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ q.close()
+ expect(4)
+ }
+ expect(2)
+ for (x in q) {
+ expectUnreached()
+ }
+ finish(5)
+ }
+
+ @Test
+ fun testIteratorOne() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ q.send(1)
+ expect(4)
+ q.close()
+ expect(5)
+ }
+ expect(2)
+ for (x in q) {
+ expect(6)
+ assertEquals(1, x)
+ }
+ finish(7)
+ }
+
+ @Test
+ fun testIteratorOneWithYield() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ q.send(1) // will suspend
+ expect(6)
+ q.close()
+ expect(7)
+ }
+ expect(2)
+ yield() // yield to sender coroutine right before starting for loop
+ expect(4)
+ for (x in q) {
+ expect(5)
+ assertEquals(1, x)
+ }
+ finish(8)
+ }
+
+ @Test
+ fun testIteratorTwo() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ q.send(1)
+ expect(4)
+ q.send(2)
+ expect(7)
+ q.close()
+ expect(8)
+ }
+ expect(2)
+ for (x in q) {
+ when (x) {
+ 1 -> expect(5)
+ 2 -> expect(6)
+ else -> expectUnreached()
+ }
+ }
+ finish(9)
+ }
+
+ @Test
+ fun testIteratorTwoWithYield() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ q.send(1) // will suspend
+ expect(6)
+ q.send(2)
+ expect(7)
+ q.close()
+ expect(8)
+ }
+ expect(2)
+ yield() // yield to sender coroutine right before starting for loop
+ expect(4)
+ for (x in q) {
+ when (x) {
+ 1 -> expect(5)
+ 2 -> expect(9)
+ else -> expectUnreached()
+ }
+ }
+ finish(10)
+ }
+
+ @Test
+ fun testSuspendSendOnClosedChannel() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(4)
+ q.send(42) // suspend
+ expect(11)
+ }
+ expect(2)
+ launch {
+ expect(5)
+ q.close()
+ expect(6)
+ }
+ expect(3)
+ yield() // to sender
+ expect(7)
+ yield() // try to resume sender (it will not resume despite the close!)
+ expect(8)
+ assertEquals(42, q.receiveOrNull())
+ expect(9)
+ assertNull(q.receiveOrNull())
+ expect(10)
+ yield() // to sender, it was resumed!
+ finish(12)
+ }
+
+ class BadClass {
+ override fun equals(other: Any?): Boolean = error("equals")
+ override fun hashCode(): Int = error("hashCode")
+ override fun toString(): String = error("toString")
+ }
+
+ @Test
+ fun testProduceBadClass() = runTest {
+ val bad = BadClass()
+ val c = produce {
+ expect(1)
+ send(bad)
+ }
+ assertSame(c.receive(), bad)
+ finish(2)
+ }
+
+ @Test
+ fun testConsumeAll() = runTest {
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ for (i in 1..10) {
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(i)
+ q.send(i) // suspends
+ expectUnreached() // will get cancelled by cancel
+ }
+ }
+ expect(11)
+ q.cancel()
+ check(q.isClosedForSend)
+ check(q.isClosedForReceive)
+ assertFailsWith<CancellationException> { q.receiveOrNull() }
+ finish(12)
+ }
+
+ @Test
+ fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
+ val channel = Channel<Int>(Channel.RENDEZVOUS)
+ channel.cancel(TestCancellationException())
+ channel.receiveOrNull()
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt b/kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt
new file mode 100644
index 00000000..b9aa9990
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016-2018 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.coroutines.*
+import kotlin.test.*
+
+class SendReceiveStressTest : TestBase() {
+
+ // Emulate parametrized by hand :(
+
+ @Test
+ fun testArrayChannel() = runTest {
+ testStress(Channel(2))
+ }
+
+ @Test
+ fun testLinkedListChannel() = runTest {
+ testStress(Channel(Channel.UNLIMITED))
+ }
+
+ @Test
+ fun testRendezvousChannel() = runTest {
+ testStress(Channel(Channel.RENDEZVOUS))
+ }
+
+ private suspend fun testStress(channel: Channel<Int>) = coroutineScope {
+ val n = 100 // Do not increase, otherwise node.js will fail with timeout :(
+ val sender = launch {
+ for (i in 1..n) {
+ channel.send(i)
+ }
+ expect(2)
+ }
+ val receiver = launch {
+ for (i in 1..n) {
+ val next = channel.receive()
+ check(next == i)
+ }
+ expect(3)
+ }
+ expect(1)
+ sender.join()
+ receiver.join()
+ finish(4)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt
new file mode 100644
index 00000000..d58c05da
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.channels
+
+enum class TestBroadcastChannelKind {
+ ARRAY_1 {
+ override fun <T> create(): BroadcastChannel<T> = BroadcastChannel(1)
+ override fun toString(): String = "ArrayBroadcastChannel(1)"
+ },
+ ARRAY_10 {
+ override fun <T> create(): BroadcastChannel<T> = BroadcastChannel(10)
+ override fun toString(): String = "ArrayBroadcastChannel(10)"
+ },
+ CONFLATED {
+ override fun <T> create(): BroadcastChannel<T> = ConflatedBroadcastChannel()
+ override fun toString(): String = "ConflatedBroadcastChannel"
+ override val isConflated: Boolean get() = true
+ }
+ ;
+
+ abstract fun <T> create(): BroadcastChannel<T>
+ open val isConflated: Boolean get() = false
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt
new file mode 100644
index 00000000..27c58165
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.selects.*
+
+enum class TestChannelKind {
+ RENDEZVOUS {
+ override fun create(): Channel<Int> = Channel(Channel.RENDEZVOUS)
+ override fun toString(): String = "RendezvousChannel"
+ },
+ ARRAY_1 {
+ override fun create(): Channel<Int> = Channel(1)
+ override fun toString(): String = "ArrayChannel(1)"
+ },
+ ARRAY_10 {
+ override fun create(): Channel<Int> = Channel(10)
+ override fun toString(): String = "ArrayChannel(10)"
+ },
+ LINKED_LIST {
+ override fun create(): Channel<Int> = Channel(Channel.UNLIMITED)
+ override fun toString(): String = "LinkedListChannel"
+ },
+ CONFLATED {
+ override fun create(): Channel<Int> = Channel(Channel.CONFLATED)
+ override fun toString(): String = "ConflatedChannel"
+ override val isConflated: Boolean get() = true
+ },
+ ARRAY_BROADCAST_1 {
+ override fun create(): Channel<Int> = ChannelViaBroadcast(BroadcastChannel(1))
+ override fun toString(): String = "ArrayBroadcastChannel(1)"
+ },
+ ARRAY_BROADCAST_10 {
+ override fun create(): Channel<Int> = ChannelViaBroadcast(BroadcastChannel(10))
+ override fun toString(): String = "ArrayBroadcastChannel(10)"
+ },
+ CONFLATED_BROADCAST {
+ override fun create(): Channel<Int> = ChannelViaBroadcast(ConflatedBroadcastChannel<Int>())
+ override fun toString(): String = "ConflatedBroadcastChannel"
+ override val isConflated: Boolean get() = true
+ }
+ ;
+
+ abstract fun create(): Channel<Int>
+ open val isConflated: Boolean get() = false
+}
+
+private class ChannelViaBroadcast<E>(
+ private val broadcast: BroadcastChannel<E>
+): Channel<E>, SendChannel<E> by broadcast {
+ val sub = broadcast.openSubscription()
+
+ override val isClosedForReceive: Boolean get() = sub.isClosedForReceive
+ 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 fun iterator(): ChannelIterator<E> = sub.iterator()
+
+ override fun cancel(cause: CancellationException?) = sub.cancel(cause)
+
+ // implementing hidden method anyway, so can cast to an internal class
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
+ override fun cancel(cause: Throwable?): Boolean = (sub as AbstractChannel).cancelInternal(cause)
+
+ 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
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt b/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt
new file mode 100644
index 00000000..e016b031
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2016-2019 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.intrinsics.*
+import kotlin.coroutines.*
+import kotlin.reflect.*
+import kotlin.test.*
+
+class FlowInvariantsTest : TestBase() {
+
+ private fun <T> runParametrizedTest(
+ expectedException: KClass<out Throwable>? = null,
+ testBody: suspend (flowFactory: (suspend FlowCollector<T>.() -> Unit) -> Flow<T>) -> Unit
+ ) = runTest {
+ val r1 = runCatching { testBody { flow(it) } }.exceptionOrNull()
+ check(r1, expectedException)
+ reset()
+
+ val r2 = runCatching { testBody { abstractFlow(it) } }.exceptionOrNull()
+ check(r2, expectedException)
+ }
+
+ private fun <T> abstractFlow(block: suspend FlowCollector<T>.() -> Unit): Flow<T> = object : AbstractFlow<T>() {
+ override suspend fun collectSafely(collector: FlowCollector<T>) {
+ collector.block()
+ }
+ }
+
+ private fun check(exception: Throwable?, expectedException: KClass<out Throwable>?) {
+ if (expectedException != null && exception == null) fail("Expected $expectedException, but test completed successfully")
+ if (expectedException != null && exception != null) assertTrue(expectedException.isInstance(exception))
+ if (expectedException == null && exception != null) throw exception
+ }
+
+ @Test
+ fun testWithContextContract() = runParametrizedTest<Int>(IllegalStateException::class) { flow ->
+ flow {
+ kotlinx.coroutines.withContext(NonCancellable) {
+ emit(1)
+ }
+ }.collect {
+ assertEquals(1, it)
+ }
+ }
+
+ @Test
+ fun testWithDispatcherContractViolated() = runParametrizedTest<Int>(IllegalStateException::class) { flow ->
+ flow {
+ kotlinx.coroutines.withContext(NamedDispatchers("foo")) {
+ emit(1)
+ }
+ }.collect {
+ fail()
+ }
+ }
+
+ @Test
+ fun testCachedInvariantCheckResult() = runParametrizedTest<Int> { flow ->
+ flow {
+ emit(1)
+
+ try {
+ kotlinx.coroutines.withContext(NamedDispatchers("foo")) {
+ emit(1)
+ }
+ fail()
+ } catch (e: IllegalStateException) {
+ expect(2)
+ }
+
+ emit(3)
+ }.collect {
+ expect(it)
+ }
+ finish(4)
+ }
+
+ @Test
+ fun testWithNameContractViolated() = runParametrizedTest<Int>(IllegalStateException::class) { flow ->
+ flow {
+ kotlinx.coroutines.withContext(CoroutineName("foo")) {
+ emit(1)
+ }
+ }.collect {
+ fail()
+ }
+ }
+
+ @Test
+ fun testWithContextDoesNotChangeExecution() = runTest {
+ val flow = flow {
+ emit(NamedDispatchers.name())
+ }.flowOn(NamedDispatchers("original"))
+
+ var result = "unknown"
+ withContext(NamedDispatchers("misc")) {
+ flow
+ .flowOn(NamedDispatchers("upstream"))
+ .launchIn(this + NamedDispatchers("consumer")) {
+ onEach {
+ result = it
+ }
+ }.join()
+ }
+
+ assertEquals("original", result)
+ }
+
+ @Test
+ fun testScopedJob() = runParametrizedTest<Int>(IllegalStateException::class) { flow ->
+ flow { emit(1) }.buffer(EmptyCoroutineContext, flow).collect {
+ expect(1)
+ }
+
+ finish(2)
+ }
+
+ @Test
+ fun testScopedJobWithViolation() = runParametrizedTest<Int>(IllegalStateException::class) { flow ->
+ flow { emit(1) }.buffer(Dispatchers.Unconfined, flow).collect {
+ expect(1)
+ }
+
+ finish(2)
+ }
+
+ @Test
+ fun testMergeViolation() = runParametrizedTest<Int> { flow ->
+ fun Flow<Int>.merge(other: Flow<Int>): Flow<Int> = flow {
+ coroutineScope {
+ launch {
+ collect { value -> emit(value) }
+ }
+ other.collect { value -> emit(value) }
+ }
+ }
+
+ fun Flow<Int>.trickyMerge(other: Flow<Int>): Flow<Int> = flow {
+ coroutineScope {
+ launch {
+ collect { value ->
+ coroutineScope { emit(value) }
+ }
+ }
+ other.collect { value -> emit(value) }
+ }
+ }
+
+ val flow = flowOf(1)
+ assertFailsWith<IllegalStateException> { flow.merge(flow).toList() }
+ assertFailsWith<IllegalStateException> { flow.trickyMerge(flow).toList() }
+ }
+
+ @Test
+ fun testNoMergeViolation() = runTest {
+ fun Flow<Int>.merge(other: Flow<Int>): Flow<Int> = channelFlow {
+ launch {
+ collect { value -> send(value) }
+ }
+ other.collect { value -> send(value) }
+ }
+
+ fun Flow<Int>.trickyMerge(other: Flow<Int>): Flow<Int> = channelFlow {
+ coroutineScope {
+ launch {
+ collect { value ->
+ coroutineScope { send(value) }
+ }
+ }
+ other.collect { value -> send(value) }
+ }
+ }
+
+ val flow = flowOf(1)
+ assertEquals(listOf(1, 1), flow.merge(flow).toList())
+ assertEquals(listOf(1, 1), flow.trickyMerge(flow).toList())
+ }
+
+ @Test
+ fun testScopedCoroutineNoViolation() = runParametrizedTest<Int> { flow ->
+ fun Flow<Int>.buffer(): Flow<Int> = flow {
+ coroutineScope {
+ val channel = produce {
+ collect {
+ send(it)
+ }
+ }
+ channel.consumeEach {
+ emit(it)
+ }
+ }
+ }
+ assertEquals(listOf(1, 1), flowOf(1, 1).buffer().toList())
+ }
+
+ private fun Flow<Int>.buffer(coroutineContext: CoroutineContext, flow: (suspend FlowCollector<Int>.() -> Unit) -> Flow<Int>): Flow<Int> = flow {
+ coroutineScope {
+ val channel = Channel<Int>()
+ launch {
+ collect { value ->
+ channel.send(value)
+ }
+ channel.close()
+ }
+
+ launch(coroutineContext) {
+ for (i in channel) {
+ emit(i)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testEmptyCoroutineContext() = runTest {
+ emptyContextTest {
+ map {
+ expect(it)
+ it + 1
+ }
+ }
+ }
+
+ @Test
+ fun testEmptyCoroutineContextTransform() = runTest {
+ emptyContextTest {
+ transform {
+ expect(it)
+ emit(it + 1)
+ }
+ }
+ }
+
+ @Test
+ fun testEmptyCoroutineContextViolation() = runTest {
+ try {
+ emptyContextTest {
+ transform {
+ expect(it)
+ kotlinx.coroutines.withContext(Dispatchers.Unconfined) {
+ emit(it + 1)
+ }
+ }
+ }
+ expectUnreached()
+ } catch (e: IllegalStateException) {
+ assertTrue(e.message!!.contains("Flow invariant is violated"))
+ finish(2)
+ }
+ }
+
+ private suspend fun emptyContextTest(block: Flow<Int>.() -> Flow<Int>) {
+ suspend fun collector(): Int {
+ var result: Int = -1
+ channelFlow {
+ send(1)
+ }.block()
+ .collect {
+ expect(it)
+ result = it
+ }
+ return result
+ }
+
+ val result = runSuspendFun { collector() }
+ assertEquals(2, result)
+ finish(3)
+ }
+
+ private suspend fun runSuspendFun(block: suspend () -> Int): Int {
+ val baseline = Result.failure<Int>(IllegalStateException("Block was suspended"))
+ var result: Result<Int> = baseline
+ block.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) { result = it })
+ while (result == baseline) yield()
+ return result.getOrThrow()
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/IdFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/IdFlowTest.kt
new file mode 100644
index 00000000..a7299cc8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/IdFlowTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016-2019 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
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlin.test.*
+
+// See https://github.com/Kotlin/kotlinx.coroutines/issues/1128
+class IdFlowTest : TestBase() {
+ @Test
+ fun testCancelInCollect() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ expect(1)
+ flow {
+ expect(2)
+ emit(1)
+ expect(3)
+ hang { finish(6) }
+ }.idScoped().collect { value ->
+ expect(4)
+ assertEquals(1, value)
+ kotlin.coroutines.coroutineContext.cancel()
+ expect(5)
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testCancelInFlow() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ expect(1)
+ flow {
+ expect(2)
+ emit(1)
+ kotlin.coroutines.coroutineContext.cancel()
+ expect(3)
+ }.idScoped().collect { value ->
+ finish(4)
+ assertEquals(1, value)
+ }
+ expectUnreached()
+ }
+}
+
+/**
+ * This flow should be "identity" function with respect to cancellation.
+ */
+private fun <T> Flow<T>.idScoped(): Flow<T> = flow {
+ coroutineScope {
+ val channel = produce {
+ collect { send(it) }
+ }
+ channel.consumeEach {
+ emit(it)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt b/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt
new file mode 100644
index 00000000..67bcbdc2
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.native.concurrent.*
+
+/**
+ * Test dispatchers that emulate multiplatform context tracking.
+ */
+@ThreadLocal
+public object NamedDispatchers {
+
+ private val stack = ArrayStack()
+
+ public fun name(): String = stack.peek() ?: error("No names on stack")
+
+ public fun nameOr(defaultValue: String): String = stack.peek() ?: defaultValue
+
+ public operator fun invoke(name: String) = named(name)
+
+ private fun named(name: String): CoroutineDispatcher = object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ stack.push(name)
+ try {
+ block.run()
+ } finally {
+ val last = stack.pop() ?: error("No names on stack")
+ require(last == name) { "Inconsistent stack: expected $name, but had $last" }
+ }
+ }
+ }
+}
+
+private class ArrayStack {
+ private var elements = arrayOfNulls<String>(16)
+ private var head = 0
+
+ public fun push(value: String) {
+ if (elements.size == head - 1) ensureCapacity()
+ elements[head++] = value
+ }
+
+ public fun peek(): String? = elements.getOrNull(head - 1)
+
+ public fun pop(): String? {
+ if (head == 0) return null
+ return elements[--head]
+ }
+
+ private fun ensureCapacity() {
+ val currentSize = elements.size
+ val newCapacity = currentSize shl 1
+ val newElements = arrayOfNulls<String>(newCapacity)
+ elements.copyInto(
+ destination = newElements,
+ startIndex = head
+ )
+ elements.copyInto(
+ destination = newElements,
+ destinationOffset = elements.size - head,
+ endIndex = head
+ )
+ elements = newElements
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt b/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt
new file mode 100644
index 00000000..9b257d93
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+private class VirtualTimeDispatcher(enclosingScope: CoroutineScope) : CoroutineDispatcher(), Delay {
+
+ private val originalDispatcher = enclosingScope.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
+ private val heap = ArrayList<TimedTask>() // TODO use MPP heap/ordered set implementation (commonize ThreadSafeHeap)
+ private var currentTime = 0L
+
+ init {
+ /*
+ * Launch "event-loop-owning" task on start of the virtual time event loop.
+ * It ensures the progress of the enclosing event-loop and polls the timed queue
+ * when the enclosing event loop is empty, emulating virtual time.
+ */
+ enclosingScope.launch {
+ while (true) {
+ val delayNanos = ThreadLocalEventLoop.currentOrNull()?.processNextEvent()
+ ?: 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
+ heap.remove(nextTask)
+ currentTime = nextTask.deadline
+ nextTask.run()
+ }
+ }
+ }
+
+ private inner class TimedTask(
+ private val runnable: Runnable,
+ @JvmField val deadline: Long
+ ) : DisposableHandle, Runnable by runnable {
+
+ override fun dispose() {
+ heap.remove(this)
+ }
+ }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ originalDispatcher.dispatch(context, block)
+ }
+
+ @ExperimentalCoroutinesApi
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean = originalDispatcher.isDispatchNeeded(context)
+
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val task = TimedTask(block, currentTime + timeMillis)
+ heap += task
+ return task
+ }
+
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ val task = TimedTask(Runnable { with(continuation) { resumeUndispatched(Unit) } }, currentTime + timeMillis)
+ heap += task
+ continuation.invokeOnCancellation { task.dispose() }
+ }
+}
+
+/**
+ * Runs a test ([TestBase.runTest]) with a virtual time source.
+ * This runner has the following constraints:
+ * 1) It works only in the event-loop environment and it is relying on it.
+ * None of the coroutines should be launched in any dispatcher different from a current
+ * 2) Regular tasks always dominate delayed ones. It means that
+ * `launch { while(true) yield() }` will block the progress of the delayed tasks
+ * 3) [TestBase.finish] should always be invoked.
+ * Given all the constraints into account, it is easy to mess up with a test and actually
+ * return from [withVirtualTime] before the test is executed completely.
+ * To decrease the probability of such error, additional `finish` constraint is added.
+ */
+public fun TestBase.withVirtualTime(block: suspend CoroutineScope.() -> Unit) = runTest {
+ withContext(Dispatchers.Unconfined) {
+ // Create a platform-independent event loop
+ val dispatcher = VirtualTimeDispatcher(this)
+ withContext(dispatcher) { block() }
+ ensureFinished()
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt
new file mode 100644
index 00000000..de5c220f
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class ChannelBuildersFlowTest : TestBase() {
+ @Test
+ fun testChannelConsumeAsFlow() = runTest {
+ val channel = produce {
+ repeat(10) {
+ send(it + 1)
+ }
+ }
+ val flow = channel.consumeAsFlow()
+ assertEquals(55, flow.sum())
+ assertFailsWith<IllegalStateException> { flow.collect() }
+ }
+
+ @Test
+ fun testConsumeAsFlowCancellation() = runTest {
+ val channel = produce(NonCancellable) { // otherwise failure will cancel scope as well
+ repeat(10) {
+ send(it + 1)
+ }
+ throw TestException()
+ }
+ val flow = channel.consumeAsFlow()
+ assertEquals(15, flow.take(5).sum())
+ // the channel should have been canceled, even though took only 5 elements
+ assertTrue(channel.isClosedForReceive)
+ assertFailsWith<IllegalStateException> { flow.collect() }
+ }
+
+ @Test
+ fun testConsumeAsFlowException() = runTest {
+ val channel = produce(NonCancellable) { // otherwise failure will cancel scope as well
+ repeat(10) {
+ send(it + 1)
+ }
+ throw TestException()
+ }
+ val flow = channel.consumeAsFlow()
+ assertFailsWith<TestException> { flow.sum() }
+ assertFailsWith<IllegalStateException> { flow.collect() }
+ }
+
+ @Test
+ fun testConsumeAsFlowProduceFusing() = runTest {
+ val channel = produce { send("OK") }
+ val flow = channel.consumeAsFlow()
+ assertSame(channel, flow.produceIn(this))
+ assertFailsWith<IllegalStateException> { flow.produceIn(this) }
+ channel.cancel()
+ }
+
+ @Test
+ fun testConsumeAsFlowProduceBuffered() = runTest {
+ expect(1)
+ val channel = produce {
+ expect(3)
+ (1..10).forEach { send(it) }
+ expect(4) // produces everything because of buffering
+ }
+ val flow = channel.consumeAsFlow().buffer() // request buffering
+ expect(2) // producer is not running yet
+ val result = flow.produceIn(this)
+ // run the flow pipeline until it consumes everything into buffer
+ while (!channel.isClosedForReceive) yield()
+ expect(5) // produced had done running (buffered stuff)
+ assertNotSame(channel, result)
+ assertFailsWith<IllegalStateException> { flow.produceIn(this) }
+ // check that we received everything
+ assertEquals((1..10).toList(), result.toList())
+ finish(6)
+ }
+
+ @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()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt
new file mode 100644
index 00000000..32c2afc6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class ChannelFlowTest : TestBase() {
+ @Test
+ fun testRegular() = runTest {
+ val flow = channelFlow {
+ assertTrue(offer(1))
+ assertTrue(offer(2))
+ assertTrue(offer(3))
+ }
+ assertEquals(listOf(1, 2, 3), flow.toList())
+ }
+
+ @Test
+ fun testBuffer() = runTest {
+ val flow = channelFlow {
+ assertTrue(offer(1))
+ assertTrue(offer(2))
+ assertFalse(offer(3))
+ }.buffer(1)
+ assertEquals(listOf(1, 2), flow.toList())
+ }
+
+ @Test
+ fun testConflated() = runTest {
+ val flow = channelFlow {
+ assertTrue(offer(1))
+ assertTrue(offer(2))
+ assertTrue(offer(3))
+ assertTrue(offer(4))
+ }.buffer(Channel.CONFLATED)
+ assertEquals(listOf(1, 4), flow.toList()) // two elements in the middle got conflated
+ }
+
+ @Test
+ fun testFailureCancelsChannel() = runTest {
+ val flow = channelFlow {
+ offer(1)
+ invokeOnClose {
+ expect(2)
+ }
+ }.onEach { throw TestException() }
+
+ expect(1)
+ assertFailsWith<TestException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testFailureInSourceCancelsConsumer() = runTest {
+ val flow = channelFlow<Int> {
+ expect(2)
+ throw TestException()
+ }.onEach { expectUnreached() }
+
+ expect(1)
+ assertFailsWith<TestException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testScopedCancellation() = runTest {
+ val flow = channelFlow<Int> {
+ expect(2)
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { expect(3) }
+ }
+ throw TestException()
+ }.onEach { expectUnreached() }
+
+ expect(1)
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testMergeOneCoroutineWithCancellation() = runTest {
+ val flow = flowOf(1, 2, 3)
+ val f = flow.mergeOneCoroutine(flow).take(2)
+ assertEquals(listOf(1, 1), f.toList())
+ }
+
+ @Test
+ fun testMergeTwoCoroutinesWithCancellation() = runTest {
+ val flow = flowOf(1, 2, 3)
+ val f = flow.mergeTwoCoroutines(flow).take(2)
+ assertEquals(listOf(1, 1), f.toList())
+ }
+
+ private fun Flow<Int>.mergeTwoCoroutines(other: Flow<Int>): Flow<Int> = channelFlow {
+ launch {
+ collect { send(it); yield() }
+ }
+ launch {
+ other.collect { send(it) }
+ }
+ }
+
+ private fun Flow<Int>.mergeOneCoroutine(other: Flow<Int>): Flow<Int> = channelFlow {
+ launch {
+ collect { send(it); yield() }
+ }
+
+ other.collect { send(it); yield() }
+ }
+
+ @Test
+ @Ignore // #1374
+ fun testBufferWithTimeout() = runTest {
+ fun Flow<Int>.bufferWithTimeout(): Flow<Int> = channelFlow {
+ expect(2)
+ launch {
+ expect(3)
+ hang {
+ expect(5)
+ }
+ }
+ launch {
+ expect(4)
+ collect {
+ withTimeout(-1) {
+ send(it)
+ }
+ expectUnreached()
+ }
+ expectUnreached()
+ }
+ }
+
+ val flow = flowOf(1, 2, 3).bufferWithTimeout()
+ expect(1)
+ assertFailsWith<TimeoutCancellationException>(flow)
+ finish(6)
+ }
+
+ @Test
+ fun testChildCancellation() = runTest {
+ channelFlow {
+ val job = launch {
+ expect(2)
+ hang { expect(4) }
+ }
+ expect(1)
+ yield()
+ expect(3)
+ job.cancelAndJoin()
+ send(5)
+
+ }.collect {
+ expect(it)
+ }
+
+ finish(6)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/channels/FlowCallbackTest.kt b/kotlinx-coroutines-core/common/test/flow/channels/FlowCallbackTest.kt
new file mode 100644
index 00000000..a6b53405
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/channels/FlowCallbackTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2019 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
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlin.test.*
+
+class FlowCallbackTest : TestBase() {
+ @Test
+ fun testClosedPrematurely() = runTest(unhandled = listOf({ e -> e is ClosedSendChannelException })) {
+ val outerScope = this
+ val flow = channelFlow {
+ // ~ callback-based API
+ outerScope.launch(Job()) {
+ expect(2)
+ send(1)
+ expectUnreached()
+ }
+ expect(1)
+ }
+ assertEquals(emptyList(), flow.toList())
+ finish(3)
+ }
+
+ @Test
+ fun testNotClosedPrematurely() = runTest {
+ val outerScope = this
+ val flow = channelFlow<Int> {
+ // ~ callback-based API
+ outerScope.launch(Job()) {
+ expect(2)
+ send(1)
+ close()
+ }
+ expect(1)
+ awaitClose()
+ }
+
+ assertEquals(listOf(1), flow.toList())
+ finish(3)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt b/kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt
new file mode 100644
index 00000000..d41ab889
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class FlowScopeTest : TestBase() {
+
+ @Test
+ fun testCancellation() = runTest {
+ assertFailsWith<CancellationException> {
+ flowScope {
+ expect(1)
+ val child = launch {
+ expect(3)
+ hang { expect(5) }
+ }
+ expect(2)
+ yield()
+ expect(4)
+ child.cancel()
+ }
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testCancellationWithChildCancelled() = runTest {
+ flowScope {
+ expect(1)
+ val child = launch {
+ expect(3)
+ hang { expect(5) }
+ }
+ expect(2)
+ yield()
+ expect(4)
+ child.cancel(ChildCancelledException())
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testCancellationWithSuspensionPoint() = runTest {
+ assertFailsWith<CancellationException> {
+ flowScope {
+ expect(1)
+ val child = launch {
+ expect(3)
+ hang { expect(6) }
+ }
+ expect(2)
+ yield()
+ expect(4)
+ child.cancel()
+ hang { expect(5) }
+ }
+ }
+ finish(7)
+ }
+
+ @Test
+ fun testNestedScopes() = runTest {
+ assertFailsWith<CancellationException> {
+ flowScope {
+ flowScope {
+ launch {
+ throw CancellationException(null)
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/BufferTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/BufferTest.kt
new file mode 100644
index 00000000..b68e1156
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/BufferTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2016-2019 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 kotlin.math.*
+import kotlin.test.*
+
+class BufferTest : TestBase() {
+ private val n = 50 // number of elements to emit for test
+ private val defaultBufferSize = 64 // expected default buffer size (per docs)
+
+ // Use capacity == -1 to check case of "no buffer"
+ private fun checkBuffer(capacity: Int, op: suspend Flow<Int>.() -> Flow<Int>) = runTest {
+ expect(1)
+ val batchSize = capacity + 2
+ flow {
+ repeat(n) { i ->
+ val batchNo = i / batchSize
+ val batchIdx = i % batchSize
+ expect(batchNo * batchSize * 2 + batchIdx + 2)
+ emit(i)
+ }
+ }
+ .op() // insert user-defined operator
+ .collect { i ->
+ val batchNo = i / batchSize
+ val batchIdx = i % batchSize
+ // last batch might have smaller size
+ val k = min((batchNo + 1) * batchSize, n) - batchNo * batchSize
+ expect(batchNo * batchSize * 2 + k + batchIdx + 2)
+ }
+ finish(2 * n + 2)
+ }
+
+ @Test
+ // capacity == -1 to checkBuffer means "no buffer" -- emits / collects are sequentially ordered
+ fun testBaseline() =
+ checkBuffer(-1) { this }
+
+ @Test
+ fun testBufferDefault() =
+ checkBuffer(defaultBufferSize) {
+ buffer()
+ }
+
+ @Test
+ fun testBufferRendezvous() =
+ checkBuffer(0) {
+ buffer(0)
+ }
+
+ @Test
+ fun testBuffer1() =
+ checkBuffer(1) {
+ buffer(1)
+ }
+
+ @Test
+ fun testBuffer2() =
+ checkBuffer(2) {
+ buffer(2)
+ }
+
+ @Test
+ fun testBuffer3() =
+ checkBuffer(3) {
+ buffer(3)
+ }
+
+ @Test
+ fun testBuffer00Fused() =
+ checkBuffer(0) {
+ buffer(0).buffer(0)
+ }
+
+ @Test
+ fun testBuffer01Fused() =
+ checkBuffer(1) {
+ buffer(0).buffer(1)
+ }
+
+ @Test
+ fun testBuffer11Fused() =
+ checkBuffer(2) {
+ buffer(1).buffer(1)
+ }
+
+ @Test
+ fun testBuffer111Fused() =
+ checkBuffer(3) {
+ buffer(1).buffer(1).buffer(1)
+ }
+
+ @Test
+ fun testBuffer123Fused() =
+ checkBuffer(6) {
+ buffer(1).buffer(2).buffer(3)
+ }
+
+ @Test // multiple calls to buffer() create one channel of default size
+ fun testBufferDefaultTwiceFused() =
+ checkBuffer(defaultBufferSize) {
+ buffer().buffer()
+ }
+
+ @Test // explicit buffer takes precedence of default buffer on fuse
+ fun testBufferDefaultBufferFused() =
+ checkBuffer(7) {
+ buffer().buffer(7)
+ }
+
+ @Test // explicit buffer takes precedence of default buffer on fuse
+ fun testBufferBufferDefaultFused() =
+ checkBuffer(8) {
+ buffer(8).buffer()
+ }
+
+ @Test // flowOn operator does not use buffer when dispatches does not change
+ fun testFlowOnNameNoBuffer() =
+ checkBuffer(-1) {
+ flowOn(CoroutineName("Name"))
+ }
+
+ @Test // flowOn operator uses default buffer size when dispatcher changes
+ fun testFlowOnDispatcherBufferDefault() =
+ checkBuffer(defaultBufferSize) {
+ flowOn(wrapperDispatcher())
+ }
+
+ @Test // flowOn(...).buffer(n) sets explicit buffer size to n
+ fun testFlowOnDispatcherBufferFused() =
+ checkBuffer(5) {
+ flowOn(wrapperDispatcher()).buffer(5)
+ }
+
+ @Test // buffer(n).flowOn(...) sets explicit buffer size to n
+ fun testBufferFlowOnDispatcherFused() =
+ checkBuffer(6) {
+ buffer(6).flowOn(wrapperDispatcher())
+ }
+
+ @Test // flowOn(...).buffer(n) sets explicit buffer size to n
+ fun testFlowOnNameBufferFused() =
+ checkBuffer(7) {
+ flowOn(CoroutineName("Name")).buffer(7)
+ }
+
+ @Test // buffer(n).flowOn(...) sets explicit buffer size to n
+ fun testBufferFlowOnNameFused() =
+ checkBuffer(8) {
+ buffer(8).flowOn(CoroutineName("Name"))
+ }
+
+ @Test // multiple flowOn/buffer all fused together
+ fun testBufferFlowOnMultipleFused() =
+ checkBuffer(12) {
+ flowOn(wrapperDispatcher()).buffer(3)
+ .flowOn(CoroutineName("Name")).buffer(4)
+ .flowOn(wrapperDispatcher()).buffer(5)
+ }
+
+ @Test
+ fun testConflate() = runTest {
+ expect(1)
+ // emit all and conflate / then collect first & last
+ flow {
+ repeat(n) { i ->
+ expect(i + 2)
+ emit(i)
+ }
+ }
+ .buffer(Channel.CONFLATED)
+ .collect { i ->
+ when (i) {
+ 0 -> expect(n + 2) // first value
+ n - 1 -> expect(n + 3) // last value
+ else -> error("Unexpected $i")
+ }
+ }
+ finish(n + 4)
+ }
+
+ @Test
+ fun testCancellation() = runTest {
+ val result = flow {
+ emit(1)
+ emit(2)
+ emit(3)
+ expectUnreached()
+ emit(4)
+ }.buffer(0)
+ .take(2)
+ .toList()
+ assertEquals(listOf(1, 2), result)
+ }
+}
+
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
new file mode 100644
index 00000000..802ba1ef
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016-2019 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.coroutines.*
+import kotlin.test.*
+
+class CatchTest : TestBase() {
+ @Test
+ fun testCatchEmit() = runTest {
+ val flow = flow {
+ emit(1)
+ throw TestException()
+ }
+
+ assertEquals(42, flow.catch { emit(41) }.sum())
+ assertFailsWith<TestException>(flow)
+ }
+
+ @Test
+ fun testCatchEmitExceptionFromDownstream() = runTest {
+ var executed = 0
+ val flow = flow {
+ emit(1)
+ }.catch { emit(42) }.map {
+ ++executed
+ throw TestException()
+ }
+
+ assertFailsWith<TestException>(flow)
+ assertEquals(1, executed)
+ }
+
+ @Test
+ fun testCatchEmitAll() = runTest {
+ val flow = flow {
+ emit(1)
+ throw TestException()
+ }.catch { emitAll(flowOf(2)) }
+
+ assertEquals(3, flow.sum())
+ }
+
+ @Test
+ fun testCatchEmitAllExceptionFromDownstream() = runTest {
+ var executed = 0
+ val flow = flow {
+ emit(1)
+ }.catch { emitAll(flowOf(1, 2, 3)) }.map {
+ ++executed
+ throw TestException()
+ }
+
+ assertFailsWith<TestException>(flow)
+ assertEquals(1, executed)
+ }
+
+ @Test
+ fun testWithTimeoutCatch() = runTest {
+ val flow = flow<Int> {
+ withTimeout(1) {
+ hang { expect(1) }
+ }
+ expectUnreached()
+ }.catch { emit(1) }
+
+ assertEquals(1, flow.single())
+ finish(2)
+ }
+
+ @Test
+ fun testCancellationFromUpstreamCatch() = runTest {
+ val flow = flow<Int> {
+ hang { }
+ }.catch { expectUnreached() }
+
+ val job = launch {
+ expect(1)
+ flow.collect { }
+ }
+
+ yield()
+ expect(2)
+ job.cancelAndJoin()
+ finish(3)
+ }
+
+ @Test
+ fun testCatchContext() = runTest {
+ expect(1)
+ val flow = flow {
+ expect(2)
+ emit("OK")
+ expect(3)
+ throw TestException()
+ }
+ val d0 = coroutineContext[ContinuationInterceptor] as CoroutineContext
+ val d1 = wrapperDispatcher(coroutineContext)
+ val d2 = wrapperDispatcher(coroutineContext)
+ flow
+ .catch { e ->
+ expect(4)
+ assertTrue(e is TestException)
+ assertEquals("A", kotlin.coroutines.coroutineContext[CoroutineName]?.name)
+ assertSame(d1, kotlin.coroutines.coroutineContext[ContinuationInterceptor] as CoroutineContext)
+ throw e // rethrow downstream
+ }
+ .flowOn(CoroutineName("A"))
+ .catch { e ->
+ expect(5)
+ assertTrue(e is TestException)
+ assertEquals("B", kotlin.coroutines.coroutineContext[CoroutineName]?.name)
+ assertSame(d1, kotlin.coroutines.coroutineContext[ContinuationInterceptor] as CoroutineContext)
+ throw e // rethrow downstream
+ }
+ .flowOn(CoroutineName("B"))
+ .catch { e ->
+ expect(6)
+ assertTrue(e is TestException)
+ assertSame(d1, kotlin.coroutines.coroutineContext[ContinuationInterceptor] as CoroutineContext)
+ throw e // rethrow downstream
+ }
+ .flowOn(d1)
+ .catch { e ->
+ expect(7)
+ assertTrue(e is TestException)
+ assertSame(d2, kotlin.coroutines.coroutineContext[ContinuationInterceptor] as CoroutineContext)
+ throw e // rethrow downstream
+ }
+ .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 ->
+ expect(8)
+ assertEquals("OK", value)
+ }
+ .catch { e ->
+ expect(9)
+ assertTrue(e is TestException)
+ assertSame(d0, kotlin.coroutines.coroutineContext[ContinuationInterceptor] as CoroutineContext)
+ }
+ .collect()
+ finish(10)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/CombineParametersTestBase.kt b/kotlinx-coroutines-core/common/test/flow/operators/CombineParametersTestBase.kt
new file mode 100644
index 00000000..a987c834
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/CombineParametersTestBase.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.operators
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.test.*
+
+class CombineParametersTest : TestBase() {
+
+ @Test
+ fun testThreeParameters() = runTest {
+ val flow = combine(flowOf("1"), flowOf(2), flowOf(null)) { a, b, c -> a + b + c }
+ assertEquals("12null", flow.single())
+
+ val flow2 = combineTransform(flowOf("1"), flowOf(2), flowOf(null)) { a, b, c -> emit(a + b + c) }
+ assertEquals("12null", flow2.single())
+ }
+
+ @Test
+ fun testThreeParametersTransform() = runTest {
+ val flow = combineTransform(flowOf("1"), flowOf(2), flowOf(null)) { a, b, c -> emit(a + b + c) }
+ assertEquals("12null", flow.single())
+ }
+
+ @Test
+ fun testFourParameters() = runTest {
+ val flow = combine(flowOf("1"), flowOf(2), flowOf("3"), flowOf(null)) { a, b, c, d -> a + b + c + d }
+ assertEquals("123null", flow.single())
+ }
+
+ @Test
+ fun testFourParametersTransform() = runTest {
+ val flow = combineTransform(flowOf("1"), flowOf(2), flowOf("3"), flowOf(null)) { a, b, c, d ->
+ emit(a + b + c + d)
+ }
+ assertEquals("123null", flow.single())
+ }
+
+ @Test
+ fun testFiveParameters() = runTest {
+ val flow = combine(flowOf("1"), flowOf(2), flowOf("3"), flowOf(4.toByte()), flowOf(null)) { a, b, c, d, e ->
+ a + b + c + d + e
+ }
+ assertEquals("1234null", flow.single())
+ }
+
+ @Test
+ fun testFiveParametersTransform() = runTest {
+ val flow =
+ combineTransform(flowOf("1"), flowOf(2), flowOf("3"), flowOf(4.toByte()), flowOf(null)) { a, b, c, d, e ->
+ emit(a + b + c + d + e)
+ }
+ assertEquals("1234null", flow.single())
+ }
+
+ @Test
+ fun testNonMatchingTypes() = runTest {
+ val flow = combine(flowOf(1), flowOf("2")) { args: Array<Any?> ->
+ args[0]?.toString() + args[1]?.toString()
+ }
+ assertEquals("12", flow.single())
+ }
+
+ @Test
+ fun testNonMatchingTypesIterable() = runTest {
+ val flow = combine(listOf(flowOf(1), flowOf("2"))) { args: Array<Any?> ->
+ args[0]?.toString() + args[1]?.toString()
+ }
+ assertEquals("12", flow.single())
+ }
+
+ @Test
+ fun testVararg() = runTest {
+ val flow = combine(
+ flowOf("1"),
+ flowOf(2),
+ flowOf("3"),
+ flowOf(4.toByte()),
+ flowOf("5"),
+ flowOf(null)
+ ) { arr -> arr.joinToString("") }
+ assertEquals("12345null", flow.single())
+ }
+
+ @Test
+ fun testVarargTransform() = runTest {
+ val flow = combineTransform(
+ flowOf("1"),
+ flowOf(2),
+ flowOf("3"),
+ flowOf(4.toByte()),
+ flowOf("5"),
+ flowOf(null)
+ ) { arr -> emit(arr.joinToString("")) }
+ assertEquals("12345null", flow.single())
+ }
+
+ @Test
+ fun testEmptyVararg() = runTest {
+ val list = combine(flowOf(1, 2, 3)) { args: Array<Any?> -> args[0] }.toList()
+ assertEquals(listOf(1, 2, 3), list)
+ }
+
+ @Test
+ fun testEmptyVarargTransform() = runTest {
+ val list = combineTransform(flowOf(1, 2, 3)) { args: Array<Any?> -> emit(args[0]) }.toList()
+ assertEquals(listOf(1, 2, 3), list)
+ }
+
+ @Test
+ fun testReified() = runTest {
+ val value = combine(flowOf(1), flowOf(2)) { args: Array<Int> ->
+ @Suppress("USELESS_IS_CHECK")
+ assertTrue(args is Array<Int>)
+ args[0] + args[1]
+ }.single()
+ assertEquals(3, value)
+ }
+
+ @Test
+ fun testReifiedTransform() = runTest {
+ val value = combineTransform(flowOf(1), flowOf(2)) { args: Array<Int> ->
+ @Suppress("USELESS_IS_CHECK")
+ assertTrue(args is Array<Int>)
+ emit(args[0] + args[1])
+ }.single()
+ assertEquals(3, value)
+ }
+
+ @Test
+ fun testEmpty() = runTest {
+ val value = combineTransform { args: Array<Int> ->
+ emit(args[0] + args[1])
+ }.singleOrNull()
+ assertNull(value)
+ }
+
+ @Test
+ fun testEmptyIterable() = runTest {
+ val value = combineTransform(emptyList()) { args: Array<Int> ->
+ emit(args[0] + args[1])
+ }.singleOrNull()
+ assertNull(value)
+ }
+
+ @Test
+ fun testEmptyReified() = runTest {
+ val value = combineTransform { args: Array<Int> ->
+ emit(args[0] + args[1])
+ }.singleOrNull()
+ assertNull(value)
+ }
+
+ @Test
+ fun testEmptyIterableReified() = runTest {
+ val value = combineTransform(emptyList()) { args: Array<Int> ->
+ emit(args[0] + args[1])
+ }.singleOrNull()
+ assertNull(value)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/CombineTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/CombineTest.kt
new file mode 100644
index 00000000..a619355b
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/CombineTest.kt
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2016-2019 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.*
+import kotlinx.coroutines.flow.combine as combineOriginal
+import kotlinx.coroutines.flow.combineTransform as combineTransformOriginal
+
+/*
+ * Replace: { i, j -> i + j } -> { i, j -> i + j } as soon as KT-30991 is fixed
+ */
+abstract class CombineTestBase : TestBase() {
+
+ abstract fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R>
+
+ @Test
+ fun testCombineLatest() = runTest {
+ val flow = flowOf("a", "b", "c")
+ val flow2 = flowOf(1, 2, 3)
+ val list = flow.combineLatest(flow2) { i, j -> i + j }.toList()
+ assertEquals(listOf("a1", "b1", "b2", "c2", "c3"), list)
+ }
+
+ @Test
+ fun testNulls() = runTest {
+ val flow = flowOf("a", null, null)
+ val flow2 = flowOf(1, 2, 3)
+ val list = flow.combineLatest(flow2, { i, j -> i + j }).toList()
+ assertEquals(listOf("a1", "null1", "null2", "null2", "null3"), list)
+ }
+
+ @Test
+ fun testNullsOther() = runTest {
+ val flow = flowOf("a", "b", "c")
+ val flow2 = flowOf(null, 2, null)
+ val list = flow.combineLatest(flow2, { i, j -> i + j }).toList()
+ assertEquals(listOf("anull", "bnull", "b2", "c2", "cnull"), list)
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ val flow = emptyFlow<String>().combineLatest(emptyFlow<Int>(), { i, j -> i + j })
+ assertNull(flow.singleOrNull())
+ }
+
+ @Test
+ fun testFirstIsEmpty() = runTest {
+ val f1 = emptyFlow<String>()
+ val f2 = flowOf(1)
+ assertEquals(emptyList(), f1.combineLatest(f2) { i, j -> i + j }.toList())
+ }
+
+ @Test
+ fun testSecondIsEmpty() = runTest {
+ val f1 = flowOf("a")
+ val f2 = emptyFlow<Int>()
+ assertEquals(emptyList(), f1.combineLatest(f2) { i, j -> i + j }.toList())
+ }
+
+ @Test
+ fun testPreservingOrder() = runTest {
+ val f1 = flow {
+ expect(1)
+ emit("a")
+ expect(3)
+ emit("b")
+ emit("c")
+ expect(4)
+ }
+
+ val f2 = flow {
+ expect(2)
+ emit(1)
+ yield()
+ yield()
+ expect(5)
+ emit(2)
+ expect(6)
+ yield()
+ expect(7)
+ emit(3)
+ }
+
+ val result = f1.combineLatest(f2) { i, j -> i + j }.toList()
+ assertEquals(listOf("a1", "b1", "c1", "c2", "c3"), result)
+ finish(8)
+ }
+
+ @Test
+ fun testPreservingOrderReversed() = runTest {
+ val f1 = flow {
+ expect(1)
+ emit("a")
+ expect(3)
+ emit("b")
+ emit("c")
+ expect(4)
+ }
+
+ val f2 = flow {
+ yield() // One more yield because now this flow starts first
+ expect(2)
+ emit(1)
+ yield()
+ yield()
+ expect(5)
+ emit(2)
+ expect(6)
+ yield()
+ expect(7)
+ emit(3)
+ }
+
+ val result = f2.combineLatest(f1) { i, j -> j + i }.toList()
+ assertEquals(listOf("a1", "b1", "c1", "c2", "c3"), result)
+ finish(8)
+ }
+
+ @Test
+ fun testContextIsIsolated() = runTest {
+ val f1 = flow {
+ emit("a")
+ assertEquals("first", NamedDispatchers.name())
+ expect(1)
+ }.flowOn(NamedDispatchers("first")).onEach {
+ assertEquals("nested", NamedDispatchers.name())
+ expect(2)
+ }.flowOn(NamedDispatchers("nested"))
+
+ val f2 = flow {
+ emit(1)
+ assertEquals("second", NamedDispatchers.name())
+ expect(3)
+ }.flowOn(NamedDispatchers("second"))
+ .onEach {
+ assertEquals("onEach", NamedDispatchers.name())
+ expect(4)
+ }.flowOn(NamedDispatchers("onEach"))
+
+ val value = withContext(NamedDispatchers("main")) {
+ f1.combineLatest(f2) { i, j ->
+ assertEquals("main", NamedDispatchers.name())
+ expect(5)
+ i + j
+ }.single()
+ }
+
+ assertEquals("a1", value)
+ finish(6)
+ }
+
+ @Test
+ fun testErrorInDownstreamCancelsUpstream() = runTest {
+ val f1 = flow {
+ emit("a")
+ hang {
+ expect(2)
+ }
+ }.flowOn(NamedDispatchers("first"))
+
+ val f2 = flow {
+ emit(1)
+ hang {
+ expect(3)
+ }
+ }.flowOn(NamedDispatchers("second"))
+
+ val flow = f1.combineLatest(f2) { i, j ->
+ assertEquals("combine", NamedDispatchers.name())
+ expect(1)
+ i + j
+ }.flowOn(NamedDispatchers("combine")).onEach {
+ throw TestException()
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testErrorCancelsSibling() = runTest {
+ val f1 = flow {
+ emit("a")
+ hang {
+ expect(1)
+ }
+ }.flowOn(NamedDispatchers("first"))
+
+ val f2 = flow {
+ emit(1)
+ throw TestException()
+ }.flowOn(NamedDispatchers("second"))
+
+ val flow = f1.combineLatest(f2) { _, _ -> 1 }
+ assertFailsWith<TestException>(flow)
+ finish(2)
+ }
+
+ @Test
+ fun testCancellationExceptionUpstream() = runTest {
+ val f1 = flow {
+ expect(1)
+ emit(1)
+ throw CancellationException("")
+ }
+ val f2 = flow {
+ emit(1)
+ hang { expect(3) }
+ }
+
+ val flow = f1.combineLatest(f2, { _, _ -> 1 }).onEach { expect(2) }
+ assertFailsWith<CancellationException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testCancellationExceptionDownstream() = runTest {
+ val f1 = flow {
+ emit(1)
+ expect(2)
+ hang { expect(5) }
+ }
+ val f2 = flow {
+ emit(1)
+ expect(3)
+ hang { expect(6) }
+ }
+
+ val flow = f1.combineLatest(f2, { _, _ -> 1 }).onEach {
+ expect(1)
+ yield()
+ expect(4)
+ throw CancellationException("")
+ }
+ assertFailsWith<CancellationException>(flow)
+ finish(7)
+ }
+}
+
+class CombineTest : CombineTestBase() {
+ override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = combineOriginal(other, transform)
+}
+
+class CombineTransformTest : CombineTestBase() {
+ override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = combineTransformOriginal(other) { a, b ->
+ emit(transform(a, b))
+ }
+}
+
+class CombineVarargAdapterTest : CombineTestBase() {
+ override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> =
+ combineOriginal(this, other) { args: Array<Any?> -> transform(args[0] as T1, args[1] as T2) }
+}
+
+class CombineIterableTest : CombineTestBase() {
+ override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> =
+ combineOriginal(listOf(this, other)) { args -> transform(args[0] as T1, args[1] as T2) }
+}
+
+class CombineTransformAdapterTest : CombineTestBase() {
+ override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> =
+ combineTransformOriginal(flow = this, flow2 = other) { a1, a2 -> emit(transform(a1, a2)) }
+}
+
+class CombineTransformVarargAdapterTest : CombineTestBase() {
+ override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> =
+ combineTransformOriginal(this, other) { args: Array<Any?> -> emit(transform(args[0] as T1, args[1] as T2)) }
+}
+
+class CombineTransformIterableTest : CombineTestBase() {
+ override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> =
+ combineTransformOriginal(listOf(this, other)) { args -> emit(transform(args[0] as T1, args[1] as T2)) }
+}
+
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/ConflateTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/ConflateTest.kt
new file mode 100644
index 00000000..264aba08
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/ConflateTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.operators
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.test.*
+
+class ConflateTest : TestBase() {
+ @Test // from example
+ fun testExample() = withVirtualTime {
+ expect(1)
+ val flow = flow {
+ for (i in 1..30) {
+ delay(100)
+ emit(i)
+ }
+ }
+ val result = flow.conflate().onEach {
+ delay(1000)
+ }.toList()
+ assertEquals(listOf(1, 10, 20, 30), result)
+ finish(2)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
new file mode 100644
index 00000000..2a6e9c12
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class DebounceTest : TestBase() {
+ @Test
+ public fun testBasic() = withVirtualTime {
+ expect(1)
+ val flow = flow {
+ expect(3)
+ emit("A")
+ delay(1500)
+ emit("B")
+ delay(500)
+ emit("C")
+ delay(250)
+ emit("D")
+ delay(2000)
+ emit("E")
+ expect(4)
+ }
+
+ expect(2)
+ val result = flow.debounce(1000).toList()
+ assertEquals(listOf("A", "D", "E"), result)
+ finish(5)
+ }
+
+ @Test
+ fun testSingleNull() = runTest {
+ val flow = flowOf<Int?>(null).debounce(Long.MAX_VALUE)
+ assertNull(flow.single())
+ }
+
+ @Test
+ fun testBasicWithNulls() = withVirtualTime {
+ expect(1)
+ val flow = flow {
+ expect(3)
+ emit("A")
+ delay(1500)
+ emit("B")
+ delay(500)
+ emit("C")
+ delay(250)
+ emit(null)
+ delay(2000)
+ emit(null)
+ expect(4)
+ }
+
+ expect(2)
+ val result = flow.debounce(1000).toList()
+ assertEquals(listOf("A", null, null), result)
+ finish(5)
+ }
+
+ @Test
+ fun testEmpty() = runTest {
+ val flow = emptyFlow<Int>().debounce(Long.MAX_VALUE)
+ assertNull(flow.singleOrNull())
+ }
+
+ @Test
+ fun testScalar() = withVirtualTime {
+ val flow = flowOf(1, 2, 3).debounce(1000)
+ assertEquals(3, flow.single())
+ finish(1)
+ }
+
+ @Test
+ fun testPace() = withVirtualTime {
+ val flow = flow {
+ expect(1)
+ repeat(10) {
+ emit(-it)
+ delay(99)
+ }
+
+ repeat(10) {
+ emit(it)
+ delay(101)
+ }
+ expect(2)
+ }.debounce(100)
+
+ assertEquals((0..9).toList(), flow.toList())
+ finish(3)
+ }
+
+ @Test
+ fun testUpstreamError()= testUpstreamError(TimeoutCancellationException(""))
+
+ @Test
+ fun testUpstreamErrorCancellation() = testUpstreamError(TimeoutCancellationException(""))
+
+ private inline fun <reified T: Throwable> testUpstreamError(cause: T) = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ latch.receive()
+ throw cause
+ }.debounce(1).map {
+ latch.send(Unit)
+ hang { expect(3) }
+ }
+
+ assertFailsWith<T>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testUpstreamErrorIsolatedContext() = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ assertEquals("upstream", NamedDispatchers.name())
+ expect(1)
+ emit(1)
+ expect(2)
+ latch.receive()
+ throw TestException()
+ }.flowOn(NamedDispatchers("upstream")).debounce(1).map {
+ latch.send(Unit)
+ hang { expect(3) }
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testUpstreamErrorDebounceNotTriggered() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ throw TestException()
+ }.debounce(Long.MAX_VALUE).map {
+ expectUnreached()
+ }
+ assertFailsWith<TestException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testUpstreamErrorDebounceNotTriggeredInIsolatedContext() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ throw TestException()
+ }.flowOn(NamedDispatchers("source")).debounce(Long.MAX_VALUE).map {
+ expectUnreached()
+ }
+ assertFailsWith<TestException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testDownstreamError() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ hang { expect(3) }
+ }.debounce(100).map {
+ expect(2)
+ yield()
+ throw TestException()
+ it
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testDownstreamErrorIsolatedContext() = runTest {
+ val flow = flow {
+ assertEquals("upstream", NamedDispatchers.name())
+ expect(1)
+ emit(1)
+ hang { expect(3) }
+ }.flowOn(NamedDispatchers("upstream")).debounce(100).map {
+ expect(2)
+ yield()
+ throw TestException()
+ it
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DistinctUntilChangedTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DistinctUntilChangedTest.kt
new file mode 100644
index 00000000..fc03d367
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DistinctUntilChangedTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016-2019 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 DistinctUntilChangedTest : TestBase() {
+
+ private class Box(val i: Int)
+
+ @Test
+ fun testDistinctUntilChanged() = runTest {
+ val flow = flowOf(1, 1, 2, 2, 1).distinctUntilChanged()
+ assertEquals(4, flow.sum())
+ }
+
+ @Test
+ fun testDistinctUntilChangedKeySelector() = runTest {
+ val flow = flow {
+ emit(Box(1))
+ emit(Box(1))
+ emit(Box(2))
+ emit(Box(1))
+ }
+
+ val sum1 = flow.distinctUntilChanged().map { it.i }.sum()
+ val sum2 = flow.distinctUntilChangedBy(Box::i).map { it.i }.sum()
+ assertEquals(5, sum1)
+ assertEquals(4, sum2)
+ }
+
+ @Test
+ fun testDistinctUntilChangedAreEquivalent() = runTest {
+ val flow = flow {
+ emit(Box(1))
+ emit(Box(1))
+ emit(Box(2))
+ emit(Box(1))
+ }
+
+ val sum1 = flow.distinctUntilChanged().map { it.i }.sum()
+ val sum2 = flow.distinctUntilChanged { old, new -> old.i == new.i }.map { it.i }.sum()
+ assertEquals(5, sum1)
+ assertEquals(4, sum2)
+ }
+
+ @Test
+ fun testDistinctUntilChangedAreEquivalentSingleValue() = runTest {
+ val flow = flowOf(1)
+ val values = flow.distinctUntilChanged { _, _ -> fail("Expected not to compare single value.") }.toList()
+ assertEquals(listOf(1), values)
+ }
+
+ @Test
+ fun testThrowingKeySelector() = runTest {
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { expect(3) }
+ }
+ expect(2)
+ emit(1)
+ }
+ }.distinctUntilChangedBy { throw TestException() }
+
+ expect(1)
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testThrowingAreEquivalent() = runTest {
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { expect(3) }
+ }
+ expect(2)
+ emit(1)
+ emit(2)
+ }
+ }.distinctUntilChanged { _, _ -> throw TestException() }
+
+ expect(1)
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testDistinctUntilChangedNull() = runTest {
+ val flow = flowOf(null, 1, null, null).distinctUntilChanged()
+ assertEquals(listOf(null, 1, null), flow.toList())
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt
new file mode 100644
index 00000000..1c5a3053
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2019 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 DropTest : TestBase() {
+ @Test
+ fun testDrop() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ emit(3)
+ }
+
+ assertEquals(5, flow.drop(1).sum())
+ assertEquals(0, flow.drop(Int.MAX_VALUE).sum())
+ assertNull(flow.drop(Int.MAX_VALUE).singleOrNull())
+ assertEquals(3, flow.drop(1).take(2).drop(1).single())
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ assertEquals(0, flowOf<Int>().drop(1).sum())
+ }
+
+ @Test
+ fun testNegativeCount() {
+ assertFailsWith<IllegalArgumentException> {
+ emptyFlow<Int>().drop(-1)
+ }
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { expect(5) }
+ }
+ expect(2)
+ emit(1)
+ expect(3)
+ emit(2)
+ expectUnreached()
+ }
+ }.drop(1)
+ .map {
+ expect(4)
+ throw TestException()
+ 42
+ }.catch { emit(42) }
+
+ expect(1)
+ assertEquals(42, flow.single())
+ finish(6)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DropWhileTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DropWhileTest.kt
new file mode 100644
index 00000000..088954bc
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DropWhileTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-2019 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 DropWhileTest : TestBase() {
+ @Test
+ fun testDropWhile() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ emit(3)
+ }
+
+ assertEquals(6, flow.dropWhile { false }.sum())
+ assertNull(flow.dropWhile { true }.singleOrNull())
+ assertEquals(5, flow.dropWhile { it < 2 }.sum())
+ assertEquals(1, flow.take(1).dropWhile { it > 1 }.single())
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ assertEquals(0, flowOf<Int>().dropWhile { true }.sum())
+ assertEquals(0, flowOf<Int>().dropWhile { false }.sum())
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { expect(4) }
+ }
+ expect(2)
+ emit(1)
+ expectUnreached()
+ }
+ }.dropWhile {
+ expect(3)
+ throw TestException()
+ }
+
+ expect(1)
+ assertFailsWith<TestException>(flow)
+ finish(5)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt
new file mode 100644
index 00000000..3de5d54a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class FilterTest : TestBase() {
+ @Test
+ fun testFilter() = runTest {
+ val flow = flowOf(1, 2)
+ assertEquals(2, flow.filter { it % 2 == 0 }.sum())
+ assertEquals(3, flow.filter { true }.sum())
+ assertEquals(0, flow.filter { false }.sum())
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ val sum = emptyFlow<Int>().filter { true }.sum()
+ assertEquals(0, sum)
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ var cancelled = false
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ hang {cancelled = true}
+ }
+ emit(1)
+ }
+ }.filter {
+ latch.receive()
+ throw TestException()
+ true
+ }.catch { emit(42) }
+
+ assertEquals(42, flow.single())
+ assertTrue(cancelled)
+ }
+
+
+ @Test
+ fun testFilterNot() = runTest {
+ val flow = flowOf(1, 2)
+ assertEquals(0, flow.filterNot { true }.sum())
+ assertEquals(3, flow.filterNot { false }.sum())
+ }
+
+ @Test
+ fun testEmptyFlowFilterNot() = runTest {
+ val sum = emptyFlow<Int>().filterNot { true }.sum()
+ assertEquals(0, sum)
+ }
+
+ @Test
+ fun testErrorCancelsUpstreamwFilterNot() = runTest {
+ var cancelled = false
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ hang {cancelled = true}
+ }
+ emit(1)
+ }
+ }.filterNot {
+ latch.receive()
+ throw TestException()
+ true
+ }.catch { emit(42) }
+
+ assertEquals(42, flow.single())
+ assertTrue(cancelled)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FilterTrivialTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FilterTrivialTest.kt
new file mode 100644
index 00000000..1d3c69bc
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FilterTrivialTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class FilterTrivialTest : TestBase() {
+
+ @Test
+ fun testFilterNotNull() = runTest {
+ val flow = flowOf(1, 2, null)
+ assertEquals(3, flow.filterNotNull().sum())
+ }
+
+ @Test
+ fun testEmptyFlowNotNull() = runTest {
+ val sum = emptyFlow<Int?>().filterNotNull().sum()
+ assertEquals(0, sum)
+ }
+
+ @Test
+ fun testFilterIsInstance() = runTest {
+ val flow = flowOf("value", 2.0)
+ assertEquals(2.0, flow.filterIsInstance<Double>().single())
+ assertEquals("value", flow.filterIsInstance<String>().single())
+ }
+
+ @Test
+ fun testFilterIsInstanceNullable() = runTest {
+ val flow = flowOf(1, 2, null)
+ assertEquals(2, flow.filterIsInstance<Int>().count())
+ assertEquals(3, flow.filterIsInstance<Int?>().count())
+ }
+
+ @Test
+ fun testEmptyFlowIsInstance() = runTest {
+ val sum = emptyFlow<Int>().filterIsInstance<Int>().sum()
+ assertEquals(0, sum)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapBaseTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapBaseTest.kt
new file mode 100644
index 00000000..ca1fa25a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapBaseTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-2019 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.*
+
+abstract class FlatMapBaseTest : TestBase() {
+
+ abstract fun <T> Flow<T>.flatMap(mapper: suspend (T) -> Flow<T>): Flow<T>
+
+ @Test
+ fun testFlatMap() = runTest {
+ val n = 100
+ val sum = (1..100).asFlow()
+ .flatMap { value ->
+ // 1 + (1 + 2) + (1 + 2 + 3) + ... (1 + .. + n)
+ flow {
+ repeat(value) {
+ emit(it + 1)
+ }
+ }
+ }.sum()
+
+ assertEquals(n * (n + 1) * (n + 2) / 6, sum)
+ }
+
+ @Test
+ fun testSingle() = runTest {
+ val flow = flow {
+ repeat(100) {
+ emit(it)
+ }
+ }.flatMap { value ->
+ if (value == 99) flowOf(42)
+ else flowOf()
+ }
+
+ val value = flow.single()
+ assertEquals(42, value)
+ }
+
+ @Test
+ fun testNulls() = runTest {
+ val list = flowOf(1, null, 2).flatMap {
+ flowOf(1, null, null, 2)
+ }.toList()
+
+ assertEquals(List(3) { listOf(1, null, null, 2)}.flatten(), list)
+ }
+
+ @Test
+ fun testContext() = runTest {
+ val captured = ArrayList<String>()
+ val flow = flowOf(1)
+ .flowOn(NamedDispatchers("irrelevant"))
+ .flatMap {
+ captured += NamedDispatchers.name()
+ flow {
+ captured += NamedDispatchers.name()
+ emit(it)
+ }
+ }
+
+ flow.flowOn(NamedDispatchers("1")).sum()
+ flow.flowOn(NamedDispatchers("2")).sum()
+ assertEquals(listOf("1", "1", "2", "2"), captured)
+ }
+
+ @Test
+ fun testIsolatedContext() = runTest {
+ val flow = flowOf(1)
+ .flowOn(NamedDispatchers("irrelevant"))
+ .flatMap {
+ flow {
+ assertEquals("inner", NamedDispatchers.name())
+ emit(it)
+ }
+ }.flowOn(NamedDispatchers("inner"))
+ .flatMap {
+ flow {
+ assertEquals("outer", NamedDispatchers.name())
+ emit(it)
+ }
+ }.flowOn(NamedDispatchers("outer"))
+
+ assertEquals(1, flow.singleOrNull())
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapConcatTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapConcatTest.kt
new file mode 100644
index 00000000..8bb54fd7
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapConcatTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 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 FlatMapConcatTest : FlatMapBaseTest() {
+
+ override fun <T> Flow<T>.flatMap(mapper: suspend (T) -> Flow<T>): Flow<T> = flatMapConcat(transform = mapper)
+
+ @Test
+ fun testFlatMapConcurrency() = runTest {
+ var concurrentRequests = 0
+ val flow = (1..100).asFlow().flatMapConcat { value ->
+ flow {
+ ++concurrentRequests
+ emit(value)
+ delay(Long.MAX_VALUE)
+ }
+ }
+
+ val consumer = launch {
+ flow.collect { value ->
+ expect(value)
+ }
+ }
+
+ repeat(4) {
+ yield()
+ }
+
+ assertEquals(1, concurrentRequests)
+ consumer.cancelAndJoin()
+ finish(2)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapLatestTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapLatestTest.kt
new file mode 100644
index 00000000..ad0bda9e
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapLatestTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016-2019 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 FlatMapLatestTest : TestBase() {
+
+ @Test
+ fun testFlatMapLatest() = runTest {
+ val flow = flowOf(1, 2, 3).flatMapLatest { value ->
+ flowOf(value, value + 1)
+ }
+ assertEquals(listOf(1, 2, 2, 3, 3, 4), flow.toList())
+ }
+
+ @Test
+ fun testEmission() = runTest {
+ val list = flow {
+ repeat(5) {
+ emit(it)
+ }
+ }.flatMapLatest { flowOf(it) }.toList()
+ assertEquals(listOf(0, 1, 2, 3, 4), list)
+ }
+
+ @Test
+ fun testSwitchIntuitiveBehaviour() = runTest {
+ val flow = flowOf(1, 2, 3, 4, 5)
+ flow.flatMapLatest {
+ flow {
+ expect(it)
+ emit(it)
+ yield() // Explicit cancellation check
+ if (it != 5) expectUnreached()
+ else expect(6)
+ }
+ }.collect()
+ finish(7)
+ }
+
+ @Test
+ fun testSwitchRendevouzBuffer() = runTest {
+ val flow = flowOf(1, 2, 3, 4, 5)
+ flow.flatMapLatest {
+ flow {
+ emit(it)
+ // Reach here every uneven element because of channel's unfairness
+ expect(it)
+ }
+ }.buffer(0).onEach { expect(it + 1) }
+ .collect()
+ finish(7)
+ }
+
+ @Test
+ fun testHangFlows() = runTest {
+ val flow = listOf(1, 2, 3, 4).asFlow()
+ val result = flow.flatMapLatest { value ->
+ flow {
+ if (value != 4) hang { expect(value) }
+ emit(42)
+ }
+ }.toList()
+
+ assertEquals(listOf(42), result)
+ finish(4)
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ assertNull(emptyFlow<Int>().flatMapLatest { flowOf(1) }.singleOrNull())
+ }
+
+ @Test
+ fun testFailureInTransform() = runTest {
+ val flow = flowOf(1, 2).flatMapLatest { value ->
+ flow {
+ if (value == 1) {
+ emit(1)
+ hang { expect(1) }
+ } else {
+ expect(2)
+ throw TestException()
+ }
+ }
+ }
+ assertFailsWith<TestException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testFailureDownstream() = runTest {
+ val flow = flowOf(1).flatMapLatest { value ->
+ flow {
+ expect(1)
+ emit(value)
+ expect(2)
+ hang { expect(4) }
+ }
+ }.flowOn(NamedDispatchers("downstream")).onEach {
+ expect(3)
+ throw TestException()
+ }
+ assertFailsWith<TestException>(flow)
+ finish(5)
+ }
+
+ @Test
+ fun testFailureUpstream() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ yield()
+ expect(3)
+ throw TestException()
+ }.flatMapLatest<Int, Long> {
+ flow {
+ expect(2)
+ hang {
+ expect(4)
+ }
+ }
+ }
+ assertFailsWith<TestException>(flow)
+ finish(5)
+ }
+
+ @Test
+ fun testTake() = runTest {
+ val flow = flowOf(1, 2, 3, 4, 5).flatMapLatest { flowOf(it) }
+ assertEquals(listOf(1), flow.take(1).toList())
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
new file mode 100644
index 00000000..44376980
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+abstract class FlatMapMergeBaseTest : FlatMapBaseTest() {
+ @Test
+ fun testFailureCancellation() = runTest {
+ val flow = flow {
+ expect(2)
+ emit(1)
+ expect(3)
+ emit(2)
+ expect(4)
+ }.flatMap {
+ if (it == 1) flow {
+ hang { expect(6) }
+ } else flow<Int> {
+ expect(5)
+ throw TestException()
+ }
+ }
+
+ expect(1)
+ assertFailsWith<TestException> { flow.singleOrNull() }
+ finish(7)
+ }
+
+ @Test
+ fun testConcurrentFailure() = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ expect(2)
+ emit(1)
+ expect(3)
+ emit(2)
+ }.flatMap {
+ if (it == 1) flow<Int> {
+ expect(5)
+ latch.send(Unit)
+ hang {
+ expect(7)
+ throw TestException2()
+
+ }
+ } else {
+ expect(4)
+ latch.receive()
+ expect(6)
+ throw TestException()
+ }
+ }
+
+ expect(1)
+ assertFailsWith<TestException>(flow)
+ finish(8)
+ }
+
+ @Test
+ fun testFailureInMapOperationCancellation() = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ expect(2)
+ emit(1)
+ expect(3)
+ emit(2)
+ expectUnreached()
+ }.flatMap {
+ if (it == 1) flow<Int> {
+ expect(5)
+ latch.send(Unit)
+ hang { expect(7) }
+ } else {
+ expect(4)
+ latch.receive()
+ expect(6)
+ throw TestException()
+ }
+ }
+
+ expect(1)
+ assertFailsWith<TestException> { flow.count() }
+ finish(8)
+ }
+
+ @Test
+ abstract fun testFlatMapConcurrency()
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt
new file mode 100644
index 00000000..a92189c4
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016-2019 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 FlatMapMergeFastPathTest : FlatMapMergeBaseTest() {
+
+ override fun <T> Flow<T>.flatMap(mapper: suspend (T) -> Flow<T>): Flow<T> = flatMapMerge(transform = mapper).buffer(64)
+
+ @Test
+ override fun testFlatMapConcurrency() = runTest {
+ var concurrentRequests = 0
+ val flow = (1..100).asFlow().flatMapMerge(concurrency = 2) { value ->
+ flow {
+ ++concurrentRequests
+ emit(value)
+ delay(Long.MAX_VALUE)
+ }
+ }.buffer(64)
+
+ val consumer = launch {
+ flow.collect { value ->
+ expect(value)
+ }
+ }
+
+ repeat(4) {
+ yield()
+ }
+
+ assertEquals(2, concurrentRequests)
+ consumer.cancelAndJoin()
+ finish(3)
+ }
+
+ @Test
+ fun testCancellationExceptionDownstream() = runTest {
+ val flow = flow {
+ emit(1)
+ hang { expect(2) }
+ }.flatMapMerge {
+ flow {
+ emit(it)
+ expect(1)
+ throw CancellationException("")
+ }
+ }.buffer(64)
+
+ assertFailsWith<CancellationException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testCancellationExceptionUpstream() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ yield()
+ throw CancellationException("")
+ }.flatMapMerge {
+ flow {
+ expect(3)
+ emit(it)
+ hang { expect(4) }
+ }
+ }.buffer(64)
+
+ assertFailsWith<CancellationException>(flow)
+ finish(5)
+ }
+
+ @Test
+ fun testCancellation() = runTest {
+ val result = flow {
+ emit(1)
+ emit(2)
+ emit(3)
+ emit(4)
+ expectUnreached() // Cancelled by take
+ emit(5)
+ }.flatMapMerge(2) { v -> flow { emit(v) } }
+ .buffer(64)
+ .take(2)
+ .toList()
+ assertEquals(listOf(1, 2), result)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt
new file mode 100644
index 00000000..511a003a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-2019 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 FlatMapMergeTest : FlatMapMergeBaseTest() {
+
+ override fun <T> Flow<T>.flatMap(mapper: suspend (T) -> Flow<T>): Flow<T> = flatMapMerge(transform = mapper)
+
+ @Test
+ override fun testFlatMapConcurrency() = runTest {
+ var concurrentRequests = 0
+ val flow = (1..100).asFlow().flatMapMerge(concurrency = 2) { value ->
+ flow {
+ ++concurrentRequests
+ emit(value)
+ delay(Long.MAX_VALUE)
+ }
+ }
+
+ val consumer = launch {
+ flow.collect { value ->
+ expect(value)
+ }
+ }
+
+ repeat(4) {
+ yield()
+ }
+
+ assertEquals(2, concurrentRequests)
+ consumer.cancelAndJoin()
+ finish(3)
+ }
+
+ @Test
+ fun testCancellationExceptionDownstream() = runTest {
+ val flow = flow {
+ emit(1)
+ hang { expect(2) }
+ }.flatMapMerge {
+ flow {
+ emit(it)
+ expect(1)
+ throw CancellationException("")
+ }
+ }
+
+ assertFailsWith<CancellationException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testCancellationExceptionUpstream() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ yield()
+ throw CancellationException("")
+ }.flatMapMerge {
+ flow {
+ expect(3)
+ emit(it)
+ hang { expect(4) }
+ }
+ }
+
+ assertFailsWith<CancellationException>(flow)
+ finish(5)
+ }
+
+ @Test
+ fun testCancellation() = runTest {
+ val result = flow {
+ emit(1)
+ emit(2)
+ emit(3)
+ emit(4)
+ expectUnreached() // Cancelled by take
+ emit(5)
+ }.flatMapMerge(2) { v -> flow { emit(v) } }
+ .take(2)
+ .toList()
+ assertEquals(listOf(1, 2), result)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt
new file mode 100644
index 00000000..084af5b9
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 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 FlattenConcatTest : FlatMapBaseTest() {
+
+ override fun <T> Flow<T>.flatMap(mapper: suspend (T) -> Flow<T>): Flow<T> = map(mapper).flattenConcat()
+
+ @Test
+ fun testFlatMapConcurrency() = runTest {
+ var concurrentRequests = 0
+ val flow = (1..100).asFlow().map { value ->
+ flow {
+ ++concurrentRequests
+ emit(value)
+ delay(Long.MAX_VALUE)
+ }
+ }.flattenConcat()
+
+ val consumer = launch {
+ flow.collect { value ->
+ expect(value)
+ }
+ }
+
+ repeat(4) {
+ yield()
+ }
+
+ assertEquals(1, concurrentRequests)
+ consumer.cancelAndJoin()
+ finish(2)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlattenMergeTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlattenMergeTest.kt
new file mode 100644
index 00000000..c15f503c
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlattenMergeTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-2019 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 FlattenMergeTest : FlatMapMergeBaseTest() {
+
+ override fun <T> Flow<T>.flatMap(mapper: suspend (T) -> Flow<T>): Flow<T> = map(mapper).flattenMerge()
+
+ @Test
+ override fun testFlatMapConcurrency() = runTest {
+ var concurrentRequests = 0
+ val flow = (1..100).asFlow().map { value ->
+ flow {
+ ++concurrentRequests
+ emit(value)
+ delay(Long.MAX_VALUE)
+ }
+ }.flattenMerge(concurrency = 2)
+
+ val consumer = launch {
+ flow.collect { value ->
+ expect(value)
+ }
+ }
+
+ repeat(4) {
+ yield()
+ }
+
+ assertEquals(2, concurrentRequests)
+ consumer.cancelAndJoin()
+ finish(3)
+ }
+
+ @Test
+ fun testContextPreservationAcrossFlows() = runTest {
+ val result = flow {
+ flowOf(1, 2).flatMapMerge {
+ flow {
+ yield()
+ emit(it)
+ }
+ }.collect {
+ emit(it)
+ }
+ }.toList()
+ assertEquals(listOf(1, 2), result)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlowContextOptimizationsTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlowContextOptimizationsTest.kt
new file mode 100644
index 00000000..bf529740
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlowContextOptimizationsTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016-2019 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.coroutines.*
+import kotlin.test.*
+import kotlin.coroutines.coroutineContext as currentContext
+
+class FlowContextOptimizationsTest : TestBase() {
+ @Test
+ fun testBaseline() = runTest {
+ val flowDispatcher = wrapperDispatcher(currentContext)
+ val collectContext = currentContext
+ flow {
+ assertSame(flowDispatcher, currentContext[ContinuationInterceptor] as CoroutineContext)
+ expect(1)
+ emit(1)
+ expect(2)
+ emit(2)
+ expect(3)
+ }
+ .flowOn(flowDispatcher)
+ .collect { value ->
+ assertEquals(collectContext.minusKey(Job), currentContext.minusKey(Job))
+ if (value == 1) expect(4)
+ else expect(5)
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testFusedSameContext() = runTest {
+ flow {
+ expect(1)
+ emit(1)
+ expect(3)
+ emit(2)
+ expect(5)
+ }
+ .flowOn(currentContext.minusKey(Job))
+ .collect { value ->
+ if (value == 1) expect(2)
+ else expect(4)
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testFusedSameContextWithIntermediateOperators() = runTest {
+ flow {
+ expect(1)
+ emit(1)
+ expect(3)
+ emit(2)
+ expect(5)
+ }
+ .flowOn(currentContext.minusKey(Job))
+ .map { it }
+ .flowOn(currentContext.minusKey(Job))
+ .collect { value ->
+ if (value == 1) expect(2)
+ else expect(4)
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testFusedSameDispatcher() = runTest {
+ flow {
+ assertEquals("Name", currentContext[CoroutineName]?.name)
+ expect(1)
+ emit(1)
+ expect(3)
+ emit(2)
+ expect(5)
+ }
+ .flowOn(CoroutineName("Name"))
+ .collect { value ->
+ assertEquals(null, currentContext[CoroutineName]?.name)
+ if (value == 1) expect(2)
+ else expect(4)
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testFusedManySameDispatcher() = runTest {
+ flow {
+ assertEquals("Name1", currentContext[CoroutineName]?.name)
+ assertEquals("OK", currentContext[CustomContextElement]?.str)
+ expect(1)
+ emit(1)
+ expect(3)
+ emit(2)
+ expect(5)
+ }
+ .flowOn(CoroutineName("Name1")) // the first one works
+ .flowOn(CoroutineName("Name2"))
+ .flowOn(CoroutineName("Name3") + CustomContextElement("OK")) // but this is not lost
+ .collect { value ->
+ assertEquals(null, currentContext[CoroutineName]?.name)
+ assertEquals(null, currentContext[CustomContextElement]?.str)
+ if (value == 1) expect(2)
+ else expect(4)
+ }
+ finish(6)
+ }
+
+ data class CustomContextElement(val str: String) : AbstractCoroutineContextElement(Key) {
+ companion object Key : CoroutineContext.Key<CustomContextElement>
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt
new file mode 100644
index 00000000..34c0476e
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class FlowOnTest : TestBase() {
+
+ @Test
+ fun testFlowOn() = runTest {
+ val source = Source(42)
+ val consumer = Consumer(42)
+
+ val flow = source::produce.asFlow()
+ flow.flowOn(NamedDispatchers("ctx1")).launchIn(this) {
+ onEach { consumer.consume(it) }
+ }.join()
+
+ assertEquals("ctx1", source.contextName)
+ assertEquals("main", consumer.contextName)
+
+ flow.flowOn(NamedDispatchers("ctx2")).launchIn(this) {
+ onEach { consumer.consume(it) }
+ }.join()
+
+ assertEquals("ctx2", source.contextName)
+ assertEquals("main", consumer.contextName)
+ }
+
+ @Test
+ fun testFlowOnAndOperators() = runTest {
+ val source = Source(42)
+ val consumer = Consumer(42)
+ val captured = ArrayList<String>()
+ val mapper: suspend (Int) -> Int = {
+ captured += NamedDispatchers.nameOr("main")
+ it
+ }
+
+ val flow = source::produce.asFlow()
+ flow.map(mapper)
+ .flowOn(NamedDispatchers("ctx1"))
+ .map(mapper)
+ .flowOn(NamedDispatchers("ctx2"))
+ .map(mapper)
+ .launchIn(this) {
+ onEach { consumer.consume(it) }
+ }.join()
+
+ assertEquals(listOf("ctx1", "ctx2", "main"), captured)
+ assertEquals("ctx1", source.contextName)
+ assertEquals("main", consumer.contextName)
+ }
+
+ @Test
+ public fun testFlowOnThrowingSource() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(NamedDispatchers.name())
+ expect(3)
+ throw TestException()
+ }.map {
+ expect(2)
+ assertEquals("throwing", it)
+ it
+ }.flowOn(NamedDispatchers("throwing"))
+
+ assertFailsWith<TestException> { flow.single() }
+ ensureActive()
+ finish(4)
+ }
+
+ @Test
+ public fun testFlowOnThrowingOperator() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(NamedDispatchers.name())
+ expectUnreached()
+ }.map {
+ expect(2)
+ assertEquals("throwing", it)
+ throw TestException(); it
+ }.flowOn(NamedDispatchers("throwing"))
+
+ assertFailsWith<TestException>(flow)
+ ensureActive()
+ finish(3)
+ }
+
+ @Test
+ public fun testFlowOnDownstreamOperator() = runTest() {
+ val flow = flow {
+ expect(2)
+ emit(NamedDispatchers.name())
+ hang { expect(5) }
+ delay(Long.MAX_VALUE)
+ }.map {
+ expect(3)
+ it
+ }.flowOn(NamedDispatchers("throwing"))
+ .map<String, String> {
+ expect(4);
+ throw TestException()
+ }
+
+ expect(1)
+ assertFailsWith<TestException> { flow.single() }
+ ensureActive()
+ finish(6)
+ }
+
+ @Test
+ public fun testFlowOnThrowingConsumer() = runTest {
+ val flow = flow {
+ expect(2)
+ emit(NamedDispatchers.name())
+ hang { expect(4) }
+ }
+
+ expect(1)
+ flow.flowOn(NamedDispatchers("...")).launchIn(this + NamedDispatchers("launch")) {
+ onEach {
+ expect(3)
+ throw TestException()
+ }
+ catch<Throwable> { expect(5) }
+ }.join()
+
+ ensureActive()
+ finish(6)
+ }
+
+ @Test
+ fun testFlowOnWithJob() = runTest({ it is IllegalArgumentException }) {
+ flow {
+ emit(1)
+ }.flowOn(NamedDispatchers("foo") + Job())
+ }
+
+ @Test
+ fun testFlowOnCancellation() = runTest {
+ val latch = Channel<Unit>()
+ expect(1)
+ val job = launch(NamedDispatchers("launch")) {
+ flow<Int> {
+ expect(2)
+ latch.send(Unit)
+ expect(3)
+ hang {
+ assertEquals("cancelled", NamedDispatchers.name())
+ expect(5)
+ }
+ }.flowOn(NamedDispatchers("cancelled")).single()
+ }
+
+ latch.receive()
+ expect(4)
+ job.cancel()
+ job.join()
+ ensureActive()
+ finish(6)
+ }
+
+ @Test
+ fun testFlowOnCancellationHappensBefore() = runTest {
+ launch {
+ try {
+ flow<Int> {
+ expect(1)
+ val flowJob = kotlin.coroutines.coroutineContext[Job]!!
+ launch {
+ expect(2)
+ flowJob.cancel()
+ }
+ hang { expect(3) }
+ }.flowOn(NamedDispatchers("upstream")).single()
+ } catch (e: CancellationException) {
+ expect(4)
+ }
+ }.join()
+ ensureActive()
+ finish(5)
+ }
+
+ @Test
+ fun testIndependentOperatorContext() = runTest {
+ val value = flow {
+ assertEquals("base", NamedDispatchers.nameOr("main"))
+ expect(1)
+ emit(-239)
+ }.map {
+ assertEquals("base", NamedDispatchers.nameOr("main"))
+ expect(2)
+ it
+ }.flowOn(NamedDispatchers("base"))
+ .map {
+ assertEquals("main", NamedDispatchers.nameOr("main"))
+ expect(3)
+ it
+ }.single()
+
+ assertEquals(-239, value)
+ finish(4)
+ }
+
+ @Test
+ fun testMultipleFlowOn() = runTest {
+ flow {
+ assertEquals("ctx1", NamedDispatchers.nameOr("main"))
+ expect(1)
+ emit(1)
+ }.map {
+ assertEquals("ctx1", NamedDispatchers.nameOr("main"))
+ expect(2)
+ }.flowOn(NamedDispatchers("ctx1"))
+ .map {
+ assertEquals("ctx2", NamedDispatchers.nameOr("main"))
+ expect(3)
+ }.flowOn(NamedDispatchers("ctx2"))
+ .map {
+ assertEquals("ctx3", NamedDispatchers.nameOr("main"))
+ expect(4)
+ }.flowOn(NamedDispatchers("ctx3"))
+ .map {
+ assertEquals("main", NamedDispatchers.nameOr("main"))
+ expect(5)
+ }
+ .single()
+
+ finish(6)
+ }
+
+ @Test
+ fun testTimeoutExceptionUpstream() = runTest {
+ val flow = flow {
+ emit(1)
+ yield()
+ withTimeout(-1) {}
+ emit(42)
+ }.flowOn(NamedDispatchers("foo")).onEach {
+ expect(1)
+ }
+ assertFailsWith<TimeoutCancellationException>(flow)
+ finish(2)
+ }
+
+ @Test
+ fun testTimeoutExceptionDownstream() = runTest {
+ val flow = flow {
+ emit(1)
+ hang { expect(2) }
+ }.flowOn(NamedDispatchers("foo")).onEach {
+ expect(1)
+ withTimeout(-1) {}
+ }
+ assertFailsWith<TimeoutCancellationException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testCancellation() = runTest {
+ val result = flow {
+ emit(1)
+ emit(2)
+ emit(3)
+ expectUnreached()
+ emit(4)
+ }.flowOn(wrapperDispatcher())
+ .buffer(0)
+ .take(2)
+ .toList()
+ assertEquals(listOf(1, 2), result)
+ }
+
+ @Test
+ fun testException() = runTest {
+ val flow = flow {
+ emit(314)
+ delay(Long.MAX_VALUE)
+ }.flowOn(NamedDispatchers("upstream"))
+ .map {
+ throw TestException()
+ }
+
+ assertFailsWith<TestException> { flow.single() }
+ assertFailsWith<TestException>(flow)
+ ensureActive()
+ }
+
+ @Test
+ fun testIllegalArgumentException() {
+ val flow = emptyFlow<Int>()
+ assertFailsWith<IllegalArgumentException> { flow.flowOn(Job()) }
+ }
+
+ private inner class Source(private val value: Int) {
+ public var contextName: String = "unknown"
+
+ fun produce(): Int {
+ contextName = NamedDispatchers.nameOr("main")
+ return value
+ }
+ }
+
+ private inner class Consumer(private val expected: Int) {
+ public var contextName: String = "unknown"
+
+ fun consume(value: Int) {
+ contextName = NamedDispatchers.nameOr("main")
+ assertEquals(expected, value)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/IndexedTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/IndexedTest.kt
new file mode 100644
index 00000000..53db88db
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/IndexedTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-2019 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 IndexedTest : TestBase() {
+
+ @Test
+ fun testWithIndex() = runTest {
+ val flow = flowOf(3, 2, 1).withIndex()
+ assertEquals(listOf(IndexedValue(0, 3), IndexedValue(1, 2), IndexedValue(2, 1)), flow.toList())
+ }
+
+ @Test
+ fun testWithIndexEmpty() = runTest {
+ val flow = emptyFlow<Int>().withIndex()
+ assertEquals(emptyList(), flow.toList())
+ }
+
+ @Test
+ fun testCollectIndexed() = runTest {
+ val result = ArrayList<IndexedValue<Long>>()
+ flowOf(3L, 2L, 1L).collectIndexed { index, value ->
+ result.add(IndexedValue(index, value))
+ }
+ assertEquals(listOf(IndexedValue(0, 3L), IndexedValue(1, 2L), IndexedValue(2, 1L)), result)
+ }
+
+ @Test
+ fun testCollectIndexedEmptyFlow() = runTest {
+ val flow = flow<Int> {
+ expect(1)
+ }
+
+ flow.collectIndexed { _, _ ->
+ expectUnreached()
+ }
+
+ finish(2)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt
new file mode 100644
index 00000000..893811df
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class MapNotNullTest : TestBase() {
+ @Test
+ fun testMap() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(null)
+ emit(2)
+ }
+
+ val result = flow.mapNotNull { it }.sum()
+ assertEquals(3, result)
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ val sum = emptyFlow<Int>().mapNotNull { expectUnreached(); it }.sum()
+ assertEquals(0, sum)
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ var cancelled = false
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ hang { cancelled = true }
+ }
+ emit(1)
+ }
+ }.mapNotNull {
+ latch.receive()
+ throw TestException()
+ it + 1
+ }.catch { emit(42) }
+
+ assertEquals(42, flow.single())
+ assertTrue(cancelled)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/MapTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/MapTest.kt
new file mode 100644
index 00000000..c744404d
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/MapTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class MapTest : TestBase() {
+ @Test
+ fun testMap() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ val result = flow.map { it + 1 }.sum()
+ assertEquals(5, result)
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ val sum = emptyFlow<Int>().map { expectUnreached(); it }.sum()
+ assertEquals(0, sum)
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ var cancelled = false
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ hang { cancelled = true }
+ }
+ emit(1)
+ }
+ }.onEach {
+ latch.receive()
+ throw TestException()
+ }.catch { emit(42) }
+
+ assertEquals(42, flow.single())
+ assertTrue(cancelled)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt
new file mode 100644
index 00000000..af50608a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2016-2019 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 OnCompletionTest : TestBase() {
+
+ @Test
+ fun testOnCompletion() = runTest {
+ flow {
+ expect(1)
+ emit(2)
+ expect(4)
+ }.onEach {
+ expect(2)
+ }.onCompletion {
+ assertNull(it)
+ expect(5)
+ }.onEach {
+ expect(3)
+ }.collect()
+ finish(6)
+ }
+
+ @Test
+ fun testOnCompletionWithException() = runTest {
+ flowOf(1).onEach {
+ expect(1)
+ throw TestException()
+ }.onCompletion {
+ assertTrue(it is TestException)
+ expect(2)
+ }.catch {
+ assertTrue(it is TestException)
+ expect(3)
+ }.collect()
+ finish(4)
+ }
+
+ @Test
+ fun testOnCompletionWithExceptionDownstream() = runTest {
+ flow {
+ expect(1)
+ emit(2)
+ }.onEach {
+ expect(2)
+ }.onCompletion {
+ assertNull(it)
+ expect(4)
+ }.onEach {
+ expect(3)
+ throw TestException()
+ }.catch {
+ assertTrue(it is TestException)
+ expect(5)
+ }.collect()
+ finish(6)
+ }
+
+ @Test
+ fun testMultipleOnCompletions() = runTest {
+ flowOf(1).onCompletion {
+ assertNull(it)
+ expect(2)
+ }.onEach {
+ expect(1)
+ throw TestException()
+ }.onCompletion {
+ assertTrue(it is TestException)
+ expect(3)
+ }.catch {
+ assertTrue(it is TestException)
+ expect(4)
+ }.collect()
+ finish(5)
+ }
+
+ @Test
+ fun testExceptionFromOnCompletion() = runTest {
+ flowOf(1).onEach {
+ expect(1)
+ throw TestException()
+ }.onCompletion {
+ expect(2)
+ throw TestException2()
+ }.catch {
+ assertTrue(it is TestException2)
+ expect(3)
+ }.collect()
+ finish(4)
+ }
+
+ @Test
+ fun testContextPreservation() = runTest {
+ flowOf(1).onCompletion {
+ assertEquals("OK", NamedDispatchers.name())
+ assertNull(it)
+ expect(1)
+ }.flowOn(NamedDispatchers("OK"))
+ .onEach {
+ expect(2)
+ assertEquals("default", NamedDispatchers.nameOr("default"))
+ throw TestException()
+ }
+ .catch {
+ assertTrue(it is TestException)
+ expect(3)
+ }.collect()
+ finish(4)
+ }
+
+ @Test
+ fun testEmitExample() = runTest {
+ val flow = flowOf("a", "b", "c")
+ .onCompletion() { emit("Done") }
+ assertEquals(listOf("a", "b", "c", "Done"), flow.toList())
+ }
+
+ sealed class TestData {
+ data class Value(val i: Int) : TestData()
+ data class Done(val e: Throwable?) : TestData() {
+ override fun equals(other: Any?): Boolean =
+ other is Done && other.e?.message == e?.message
+ }
+ }
+
+ @Test
+ fun testCrashedEmit() = runTest {
+ expect(1)
+ val collected = ArrayList<TestData>()
+ assertFailsWith<TestException> {
+ (1..10).asFlow()
+ .map<Int, TestData> { TestData.Value(it) }
+ .onEach { value ->
+ value as TestData.Value
+ expect(value.i + 1)
+ if (value.i == 6) throw TestException("OK")
+ yield()
+ }
+ .onCompletion { e ->
+ expect(8)
+ assertTrue(e is TestException)
+ emit(TestData.Done(e))
+ }.collect {
+ collected += it
+ }
+ }
+ val expected = (1..5).map { TestData.Value(it) } + TestData.Done(TestException("OK"))
+ assertEquals(expected, collected)
+ finish(9)
+ }
+
+ @Test
+ fun testCancelledEmit() = runTest {
+ expect(1)
+ val collected = ArrayList<TestData>()
+ assertFailsWith<JobCancellationException> {
+ coroutineScope {
+ (1..10).asFlow()
+ .map<Int, TestData> { TestData.Value(it) }
+ .onEach { value ->
+ value as TestData.Value
+ expect(value.i + 1)
+ if (value.i == 6) coroutineContext.cancel()
+ yield()
+ }
+ .onCompletion { e ->
+ expect(8)
+ assertNull(e)
+ emit(TestData.Done(e))
+ }.collect {
+ collected += it
+ }
+ }
+ }
+ val expected = (1..5).map { TestData.Value(it) } + TestData.Done(null)
+ assertEquals(expected, collected)
+ finish(9)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt
new file mode 100644
index 00000000..bad9db97
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class OnEachTest : TestBase() {
+ @Test
+ fun testOnEach() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ val result = flow.onEach { expect(it) }.sum()
+ assertEquals(3, result)
+ finish(3)
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ val value = emptyFlow<Int>().onEach { fail() }.singleOrNull()
+ assertNull(value)
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ var cancelled = false
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ hang { cancelled = true }
+ }
+ emit(1)
+ }
+ }.onEach {
+ latch.receive()
+ throw TestException()
+ it + 1
+ }.catch { emit(42) }
+
+ assertEquals(42, flow.single())
+ assertTrue(cancelled)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/OnStartTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/OnStartTest.kt
new file mode 100644
index 00000000..a0981ab0
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/OnStartTest.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 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 OnStartTest : TestBase() {
+ @Test
+ fun testEmitExample() = runTest {
+ val flow = flowOf("a", "b", "c")
+ .onStart { emit("Begin") }
+ assertEquals(listOf("Begin", "a", "b", "c"), flow.toList())
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt
new file mode 100644
index 00000000..b8a6b198
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016-2019 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 RetryTest : TestBase() {
+ @Test
+ fun testRetryWhen() = runTest {
+ expect(1)
+ val flow = flow {
+ emit(1)
+ throw TestException()
+ }
+ val sum = flow.retryWhen { cause, attempt ->
+ assertTrue(cause is TestException)
+ expect(2 + attempt.toInt())
+ attempt < 3
+ }.catch { cause ->
+ expect(6)
+ assertTrue(cause is TestException)
+ }.sum()
+ assertEquals(4, sum)
+ finish(7)
+ }
+
+ @Test
+ fun testRetry() = runTest {
+ var counter = 0
+ val flow = flow {
+ emit(1)
+ if (++counter < 4) throw TestException()
+ }
+
+ assertEquals(4, flow.retry(4).sum())
+ counter = 0
+ assertFailsWith<TestException>(flow)
+ counter = 0
+ assertFailsWith<TestException>(flow.retry(2))
+ }
+
+ @Test
+ fun testRetryPredicate() = runTest {
+ var counter = 0
+ val flow = flow {
+ emit(1);
+ if (++counter == 1) throw TestException()
+ }
+
+ assertEquals(2, flow.retry(1) { it is TestException }.sum())
+ counter = 0
+ assertFailsWith<TestException>(flow.retry(1) { it !is TestException })
+ }
+
+ @Test
+ fun testRetryExceptionFromDownstream() = runTest {
+ var executed = 0
+ val flow = flow {
+ emit(1)
+ }.retry(42).map {
+ ++executed
+ throw TestException()
+ }
+
+ assertFailsWith<TestException>(flow)
+ assertEquals(1, executed)
+ }
+
+ @Test
+ fun testWithTimeoutRetried() = runTest {
+ var state = 0
+ val flow = flow {
+ if (state++ == 0) {
+ expect(1)
+ withTimeout(1) {
+ hang { expect(2) }
+ }
+ expectUnreached()
+ }
+ expect(3)
+ emit(1)
+ }.retry(1)
+
+ assertEquals(1, flow.single())
+ finish(4)
+ }
+
+ @Test
+ fun testCancellationFromUpstreamIsNotRetried() = runTest {
+ val flow = flow<Int> {
+ hang { }
+ }.retry()
+
+ val job = launch {
+ expect(1)
+ flow.collect { }
+ }
+
+ yield()
+ expect(2)
+ job.cancelAndJoin()
+ finish(3)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
new file mode 100644
index 00000000..9c96352d
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.operators
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlin.test.*
+
+class SampleTest : TestBase() {
+ @Test
+ public fun testBasic() = withVirtualTime {
+ expect(1)
+ val flow = flow {
+ expect(3)
+ emit("A")
+ delay(1500)
+ emit("B")
+ delay(500)
+ emit("C")
+ delay(250)
+ emit("D")
+ delay(2000)
+ emit("E")
+ expect(4)
+ }
+
+ expect(2)
+ val result = flow.sample(1000).toList()
+ assertEquals(listOf("A", "B", "D"), result)
+ finish(5)
+ }
+
+ @Test
+ fun testDelayedFirst() = withVirtualTime {
+ val flow = flow {
+ delay(60)
+ emit(1)
+ delay(60)
+ expect(1)
+ }.sample(100)
+ assertEquals(1, flow.singleOrNull())
+ finish(2)
+ }
+
+ @Test
+ fun testBasic2() = withVirtualTime {
+ expect(1)
+ val flow = flow {
+ expect(3)
+ emit(1)
+ emit(2)
+ delay(501)
+ emit(3)
+ delay(100)
+ emit(4)
+ delay(100)
+ emit(5)
+ emit(6)
+ delay(301)
+ emit(7)
+ delay(501)
+ expect(4)
+ }
+
+ expect(2)
+ val result = flow.sample(500).toList()
+ assertEquals(listOf(2, 6, 7), result)
+ finish(5)
+ }
+
+ @Test
+ fun testFixedDelay() = withVirtualTime {
+ val flow = flow {
+ emit("A")
+ delay(150)
+ emit("B")
+ expect(1)
+ }.sample(100)
+ assertEquals("A", flow.single())
+ finish(2)
+ }
+
+ @Test
+ fun testSingleNull() = withVirtualTime {
+ val flow = flow<Int?> {
+ emit(null)
+ delay(2)
+ expect(1)
+ }.sample(1)
+ assertNull(flow.single())
+ finish(2)
+ }
+
+ @Test
+ fun testBasicWithNulls() = withVirtualTime {
+ expect(1)
+ val flow = flow {
+ expect(3)
+ emit("A")
+ delay(1500)
+ emit(null)
+ delay(500)
+ emit("C")
+ delay(250)
+ emit(null)
+ delay(2000)
+ emit("E")
+ expect(4)
+ }
+
+ expect(2)
+ val result = flow.sample(1000).toList()
+ assertEquals(listOf("A", null, null), result)
+ finish(5)
+ }
+
+ @Test
+ fun testEmpty() = runTest {
+ val flow = emptyFlow<Int>().sample(Long.MAX_VALUE)
+ assertNull(flow.singleOrNull())
+ }
+
+ @Test
+ fun testScalar() = runTest {
+ val flow = flowOf(1, 2, 3).sample(Long.MAX_VALUE)
+ assertNull(flow.singleOrNull())
+ }
+
+ @Test
+ // note that this test depends on the sampling strategy -- when sampling time starts on a quiescent flow that suddenly emits
+ fun testLongWait() = withVirtualTime {
+ expect(1)
+ val flow = flow {
+ expect(2)
+ emit("A")
+ delay(3500) // long delay -- multiple sampling intervals
+ emit("B")
+ delay(900) // crosses time = 4000 barrier
+ emit("C")
+ delay(3000) // long wait again
+
+ }
+ val result = flow.sample(1000).toList()
+ assertEquals(listOf("A", "B", "C"), result)
+ finish(3)
+ }
+
+ @Test
+ fun testPace() = withVirtualTime {
+ val flow = flow {
+ expect(1)
+ repeat(4) {
+ emit(-it)
+ delay(50)
+ }
+
+ repeat(4) {
+ emit(it)
+ delay(100)
+ }
+ expect(2)
+ }.sample(100)
+
+ assertEquals(listOf(-1, -3, 0, 1, 2, 3), flow.toList())
+ finish(3)
+ }
+
+ @Test
+ fun testUpstreamError() = testUpstreamError(TestException())
+
+ @Test
+ fun testUpstreamErrorCancellationException() = testUpstreamError(CancellationException(""))
+
+ private inline fun <reified T: Throwable> testUpstreamError(cause: T) = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ latch.receive()
+ throw cause
+ }.sample(1).map {
+ latch.send(Unit)
+ hang { expect(3) }
+ }
+
+ assertFailsWith<T>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testUpstreamErrorIsolatedContext() = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ assertEquals("upstream", NamedDispatchers.name())
+ expect(1)
+ emit(1)
+ expect(2)
+ latch.receive()
+ throw TestException()
+ }.flowOn(NamedDispatchers("upstream")).sample(1).map {
+ latch.send(Unit)
+ hang { expect(3) }
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testUpstreamErrorSampleNotTriggered() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ throw TestException()
+ }.sample(Long.MAX_VALUE).map {
+ expectUnreached()
+ }
+ assertFailsWith<TestException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testUpstreamErrorSampleNotTriggeredInIsolatedContext() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ throw TestException()
+ }.flowOn(NamedDispatchers("unused")).sample(Long.MAX_VALUE).map {
+ expectUnreached()
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testDownstreamError() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ hang { expect(3) }
+ }.sample(100).map {
+ expect(2)
+ yield()
+ throw TestException()
+ it
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testDownstreamErrorIsolatedContext() = runTest {
+ val flow = flow {
+ assertEquals("upstream", NamedDispatchers.name())
+ expect(1)
+ emit(1)
+ hang { expect(3) }
+ }.flowOn(NamedDispatchers("upstream")).sample(100).map {
+ expect(2)
+ yield()
+ throw TestException()
+ it
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt
new file mode 100644
index 00000000..0108ea17
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class ScanTest : TestBase() {
+ @Test
+ fun testScan() = runTest {
+ val flow = flowOf(1, 2, 3, 4, 5)
+ val result = flow.scanReduce { acc, v -> acc + v }.toList()
+ assertEquals(listOf(1, 3, 6, 10, 15), result)
+ }
+
+ @Test
+ fun testScanWithInitial() = runTest {
+ val flow = flowOf(1, 2, 3)
+ val result = flow.scan(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.scanReduce { acc, v -> if (v == null) acc else (if (acc == null) v else acc + v) }.toList()
+ assertEquals(listOf(null, 2, 2, 2, 2, 7), result)
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ val result = emptyFlow<Int>().scanReduce { _, _ -> 1 }.toList()
+ assertTrue(result.isEmpty())
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ expect(1)
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ hang { expect(3) }
+ }
+ emit(1)
+ emit(2)
+ }
+ }.scanReduce { _, value ->
+ expect(value) // 2
+ latch.receive()
+ throw TestException()
+ }.catch { /* ignore */ }
+
+ assertEquals(1, flow.single())
+ finish(4)
+ }
+
+ public operator fun <T> Collection<T>.plus(element: T): List<T> {
+ val result = ArrayList<T>(size + 1)
+ result.addAll(this)
+ result.add(element)
+ return result
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TakeTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TakeTest.kt
new file mode 100644
index 00000000..71103496
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/TakeTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016-2019 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 TakeTest : TestBase() {
+ @Test
+ fun testTake() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ assertEquals(3, flow.take(2).sum())
+ assertEquals(3, flow.take(Int.MAX_VALUE).sum())
+ assertEquals(1, flow.take(1).single())
+ assertEquals(2, flow.drop(1).take(1).single())
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ val sum = emptyFlow<Int>().take(10).sum()
+ assertEquals(0, sum)
+ }
+
+ @Test
+ fun testNonPositiveValues() {
+ val flow = flowOf(1)
+ assertFailsWith<IllegalArgumentException> {
+ flow.take(-1)
+ }
+
+ assertFailsWith<IllegalArgumentException> {
+ flow.take(0)
+ }
+ }
+
+ @Test
+ fun testCancelUpstream() = runTest {
+ var cancelled = false
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { cancelled = true }
+ }
+
+ emit(1)
+ }
+ }
+
+ assertEquals(1, flow.take(1).single())
+ assertTrue(cancelled)
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ var cancelled = false
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { cancelled = true }
+ }
+ emit(1)
+ }
+ }.take(2)
+ .map {
+ throw TestException()
+ 42
+ }.catch { emit(42) }
+
+ assertEquals(42, flow.single())
+ assertTrue(cancelled)
+ }
+
+ @Test
+ fun takeWithRetries() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ emit(2)
+
+ while (true) {
+ emit(42)
+ expectUnreached()
+ }
+
+ }.retry(2) {
+ expectUnreached()
+ true
+ }.take(2)
+
+ val sum = flow.sum()
+ assertEquals(3, sum)
+ finish(3)
+ }
+
+ @Test
+ fun testNonIdempotentRetry() = runTest {
+ var count = 0
+ flow { while (true) emit(1) }
+ .retry { count++ % 2 != 0 }
+ .take(1)
+ .collect {
+ expect(1)
+ }
+ finish(2)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TakeWhileTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TakeWhileTest.kt
new file mode 100644
index 00000000..c198356b
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/TakeWhileTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2019 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 TakeWhileTest : TestBase() {
+
+ @Test
+ fun testTakeWhile() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ assertEquals(3, flow.takeWhile { true }.sum())
+ assertEquals(1, flow.takeWhile { it < 2 }.single())
+ assertEquals(2, flow.drop(1).takeWhile { it < 3 }.single())
+ assertNull(flow.drop(1).takeWhile { it < 2 }.singleOrNull())
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ assertEquals(0, emptyFlow<Int>().takeWhile { true }.sum())
+ assertEquals(0, emptyFlow<Int>().takeWhile { false }.sum())
+ }
+
+ @Test
+ fun testCancelUpstream() = runTest {
+ var cancelled = false
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { cancelled = true }
+ }
+
+ emit(1)
+ emit(2)
+ }
+ }
+
+ assertEquals(1, flow.takeWhile { it < 2 }.single())
+ assertTrue(cancelled)
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ var cancelled = false
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { cancelled = true }
+ }
+ emit(1)
+ }
+ }.takeWhile {
+ throw TestException()
+ }
+
+ assertFailsWith<TestException>(flow)
+ assertTrue(cancelled)
+ assertEquals(42, flow.catch { emit(42) }.single())
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TransformLatestTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TransformLatestTest.kt
new file mode 100644
index 00000000..a37cca21
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/TransformLatestTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2016-2019 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 TransformLatestTest : TestBase() {
+
+ @Test
+ fun testTransformLatest() = runTest {
+ val flow = flowOf(1, 2, 3).transformLatest { value ->
+ emit(value)
+ emit(value + 1)
+ }
+ assertEquals(listOf(1, 2, 2, 3, 3, 4), flow.toList())
+ }
+
+ @Test
+ fun testEmission() = runTest {
+ val list = flow {
+ repeat(5) {
+ emit(it)
+ }
+ }.transformLatest {
+ emit(it)
+ }.toList()
+ assertEquals(listOf(0, 1, 2, 3, 4), list)
+ }
+
+ @Test
+ fun testSwitchIntuitiveBehaviour() = runTest {
+ val flow = flowOf(1, 2, 3, 4, 5)
+ flow.transformLatest {
+ expect(it)
+ emit(it)
+ yield() // Explicit cancellation check
+ if (it != 5) expectUnreached()
+ else expect(6)
+ }.collect()
+ finish(7)
+ }
+
+ @Test
+ fun testSwitchRendevouzBuffer() = runTest {
+ val flow = flowOf(1, 2, 3, 4, 5)
+ flow.transformLatest {
+ emit(it)
+ // Reach here every uneven element because of channel's unfairness
+ expect(it)
+ }.buffer(0).onEach { expect(it + 1) }.collect()
+ finish(7)
+ }
+
+ @Test
+ fun testSwitchBuffer() = runTest {
+ val flow = flowOf(1, 2, 3, 42, 4)
+ flow.transformLatest {
+ emit(it)
+ expect(it)
+ }.buffer(2).collect()
+ finish(5)
+ }
+
+ @Test
+ fun testHangFlows() = runTest {
+ val flow = listOf(1, 2, 3, 4).asFlow()
+ val result = flow.transformLatest { value ->
+ if (value != 4) hang { expect(value) }
+ emit(42)
+ }.toList()
+
+ assertEquals(listOf(42), result)
+ finish(4)
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ assertNull(emptyFlow<Int>().transformLatest { emit(1) }.singleOrNull())
+ }
+
+ @Test
+ fun testIsolatedContext() = runTest {
+ val flow = flow {
+ assertEquals("source", NamedDispatchers.name())
+ expect(1)
+ emit(4)
+ expect(2)
+ emit(5)
+ expect(3)
+ }.flowOn(NamedDispatchers("source")).transformLatest<Int, Int> { value ->
+ emitAll(flow<Int> {
+ assertEquals("switch$value", NamedDispatchers.name())
+ expect(value)
+ emit(value)
+ }.flowOn(NamedDispatchers("switch$value")))
+ }.onEach {
+ expect(it + 2)
+ assertEquals("main", NamedDispatchers.nameOr("main"))
+ }
+ assertEquals(2, flow.count())
+ finish(8)
+ }
+
+ @Test
+ fun testFailureInTransform() = runTest {
+ val flow = flowOf(1, 2).transformLatest { value ->
+ if (value == 1) {
+ emit(1)
+ hang { expect(1) }
+ } else {
+ expect(2)
+ throw TestException()
+ }
+ }
+ assertFailsWith<TestException>(flow)
+ finish(3)
+ }
+
+ @Test
+ fun testFailureDownstream() = runTest {
+ val flow = flowOf(1).transformLatest { value ->
+ expect(1)
+ emit(value)
+ expect(2)
+ hang { expect(4) }
+ }.flowOn(NamedDispatchers("downstream")).onEach {
+ expect(3)
+ throw TestException()
+ }
+ assertFailsWith<TestException>(flow)
+ finish(5)
+ }
+
+ @Test
+ fun testFailureUpstream() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ yield()
+ expect(3)
+ throw TestException()
+ }.transformLatest<Int, Long> {
+ expect(2)
+ hang {
+ expect(4)
+ }
+ }
+ assertFailsWith<TestException>(flow)
+ finish(5)
+ }
+
+ @Test
+ fun testTake() = runTest {
+ val flow = flowOf(1, 2, 3, 4, 5).transformLatest { emit(it) }
+ assertEquals(listOf(1), flow.take(1).toList())
+ }
+
+ @Test
+ @Ignore // TODO separate branch and/or discuss
+ fun testTakeUpstreamCancellation() = runTest {
+ val flow = flow {
+ emit(1)
+ expectUnreached()
+ emit(2)
+ emit(3)
+ }.transformLatest { emit(it) }
+ assertEquals(listOf(1), flow.take(1).toList())
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TransformTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TransformTest.kt
new file mode 100644
index 00000000..feb35964
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/TransformTest.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 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 TransformTest : TestBase() {
+ @Test
+ fun testDoubleEmit() = runTest {
+ val flow = flowOf(1, 2, 3)
+ .transform {
+ emit(it)
+ emit(it)
+ }
+ assertEquals(listOf(1, 1, 2, 2, 3, 3), flow.toList())
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/ZipTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/ZipTest.kt
new file mode 100644
index 00000000..b28320c3
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/ZipTest.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2016-2019 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.*
+
+/*
+ * Replace: { i, j -> i + j } -> ::sum as soon as KT-30991 is fixed
+ */
+class ZipTest : TestBase() {
+
+ @Test
+ fun testZip() = runTest {
+ val f1 = flowOf("a", "b", "c")
+ val f2 = flowOf(1, 2, 3)
+ assertEquals(listOf("a1", "b2", "c3"), f1.zip(f2, { i, j -> i + j }).toList())
+ }
+
+ @Test
+ fun testUnevenZip() = runTest {
+ val f1 = flowOf("a", "b", "c", "d", "e")
+ val f2 = flowOf(1, 2, 3)
+ assertEquals(listOf("a1", "b2", "c3"), f1.zip(f2, { i, j -> i + j }).toList())
+ assertEquals(listOf("a1", "b2", "c3"), f2.zip(f1) { i, j -> j + i }.toList())
+ }
+
+ @Test
+ fun testEmptyFlows() = runTest {
+ val f1 = emptyFlow<String>()
+ val f2 = emptyFlow<Int>()
+ assertEquals(emptyList(), f1.zip(f2, { i, j -> i + j }).toList())
+ }
+
+ @Test
+ fun testEmpty() = runTest {
+ val f1 = emptyFlow<String>()
+ val f2 = flowOf(1)
+ assertEquals(emptyList(), f1.zip(f2, { i, j -> i + j }).toList())
+ }
+
+ @Test
+ fun testEmptyOther() = runTest {
+ val f1 = flowOf("a")
+ val f2 = emptyFlow<Int>()
+ assertEquals(emptyList(), f1.zip(f2, { i, j -> i + j }).toList())
+ }
+
+ @Test
+ fun testNulls() = runTest {
+ val f1 = flowOf("a", null, null, "d")
+ val f2 = flowOf(1, 2, 3)
+ assertEquals(listOf("a1", "null2", "null3"), f1.zip(f2, { i, j -> i + j }).toList())
+ }
+
+ @Test
+ fun testNullsOther() = runTest {
+ val f1 = flowOf("a", "b", "c")
+ val f2 = flowOf(1, null, null, 2)
+ assertEquals(listOf("a1", "bnull", "cnull"), f1.zip(f2, { i, j -> i + j }).toList())
+ }
+
+ @Test
+ fun testCancelWhenFlowIsDone() = runTest {
+ val f1 = flow<String> {
+ emit("1")
+ emit("2")
+ hang {
+ expect(1)
+ }
+ }
+
+ val f2 = flowOf("a", "b")
+ assertEquals(listOf("1a", "2b"), f1.zip(f2) { s1, s2 -> s1 + s2 }.toList())
+ finish(2)
+ }
+
+ @Test
+ fun testCancelWhenFlowIsDoneReversed() = runTest {
+ val f1 = flow<String> {
+ emit("1")
+ emit("2")
+ hang {
+ expect(1)
+ }
+ }
+
+ val f2 = flowOf("a", "b")
+ assertEquals(listOf("a1", "b2"), f2.zip(f1) { s1, s2 -> s1 + s2 }.toList())
+ finish(2)
+ }
+
+ @Test
+ fun testCancelWhenFlowIsDone2() = runTest {
+ val f1 = flow<String> {
+ emit("1")
+ emit("2")
+ try {
+ emit("3")
+ expectUnreached()
+ } finally {
+ expect(1)
+ }
+
+ }
+
+ val f2 = flowOf("a", "b")
+ assertEquals(listOf("1a", "2b"), f1.zip(f2) { s1, s2 -> s1 + s2 }.toList())
+ finish(2)
+ }
+
+ @Test
+ fun testContextIsIsolatedReversed() = runTest {
+ val f1 = flow {
+ emit("a")
+ assertEquals("first", NamedDispatchers.name())
+ expect(1)
+ }.flowOn(NamedDispatchers("first")).onEach {
+ assertEquals("with", NamedDispatchers.name())
+ expect(2)
+ }.flowOn(NamedDispatchers("with"))
+
+ val f2 = flow {
+ emit(1)
+ assertEquals("second", NamedDispatchers.name())
+ expect(3)
+ }.flowOn(NamedDispatchers("second")).onEach {
+ assertEquals("nested", NamedDispatchers.name())
+ expect(4)
+ }.flowOn(NamedDispatchers("nested"))
+
+ val value = withContext(NamedDispatchers("main")) {
+ f1.zip(f2) { i, j ->
+ assertEquals("main", NamedDispatchers.name())
+ expect(5)
+ i + j
+ }.single()
+ }
+
+ assertEquals("a1", value)
+ finish(6)
+ }
+
+ @Test
+ fun testErrorInDownstreamCancelsUpstream() = runTest {
+ val f1 = flow {
+ emit("a")
+ hang {
+ expect(2)
+ }
+ }.flowOn(NamedDispatchers("first"))
+
+ val f2 = flow {
+ emit(1)
+ hang {
+ expect(3)
+ }
+ }.flowOn(NamedDispatchers("second"))
+
+ val flow = f1.zip(f2) { i, j ->
+ assertEquals("zip", NamedDispatchers.name())
+ expect(1)
+ i + j
+ }.flowOn(NamedDispatchers("zip")).onEach {
+ throw TestException()
+ }
+
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testErrorCancelsSibling() = runTest {
+ val f1 = flow {
+ emit("a")
+ hang {
+ expect(1)
+ }
+ }.flowOn(NamedDispatchers("first"))
+
+ val f2 = flow {
+ emit(1)
+ throw TestException()
+ }.flowOn(NamedDispatchers("second"))
+
+ val flow = f1.zip(f2) { _, _ -> 1 }
+ assertFailsWith<TestException>(flow)
+ finish(2)
+ }
+
+ @Test
+ fun testCancellationUpstream() = runTest {
+ val f1 = flow {
+ expect(1)
+ emit(1)
+ yield()
+ expect(4)
+ throw CancellationException("")
+ }
+
+ val f2 = flow {
+ expect(2)
+ emit(1)
+ expect(5)
+ hang { expect(6) }
+ }
+
+ val flow = f1.zip(f2, { _, _ -> 1 }).onEach { expect(3) }
+ assertFailsWith<CancellationException>(flow)
+ finish(7)
+ }
+
+ @Test
+ fun testCancellationDownstream() = runTest {
+ val f1 = flow {
+ expect(1)
+ emit(1)
+ yield()
+ expect(4)
+ hang { expect(6) }
+ }
+
+ val f2 = flow {
+ expect(2)
+ emit(1)
+ expect(5)
+ hang { expect(7) }
+ }
+
+ val flow = f1.zip(f2, { _, _ -> 1 }).onEach {
+ expect(3)
+ yield()
+ throw CancellationException("")
+ }
+ assertFailsWith<CancellationException>(flow)
+ finish(8)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/CollectLatestTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/CollectLatestTest.kt
new file mode 100644
index 00000000..122420c6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/CollectLatestTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2019 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 CollectLatestTest : TestBase() {
+ @Test
+ fun testNoSuspension() = runTest {
+ flowOf(1, 2, 3).collectLatest {
+ expect(it)
+ }
+ finish(4)
+ }
+
+ @Test
+ fun testSuspension() = runTest {
+ flowOf(1, 2, 3).collectLatest {
+ yield()
+ expect(1)
+ }
+ finish(2)
+ }
+
+ @Test
+ fun testUpstreamErrorSuspension() = runTest({it is TestException}) {
+ try {
+ flow {
+ emit(1)
+ throw TestException()
+ }.collectLatest { expect(1) }
+ expectUnreached()
+ } finally {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testDownstreamError() = runTest({it is TestException}) {
+ try {
+ flow {
+ emit(1)
+ hang { expect(1) }
+ }.collectLatest {
+ throw TestException()
+ }
+ expectUnreached()
+ } finally {
+ finish(2)
+ }
+
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/CountTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/CountTest.kt
new file mode 100644
index 00000000..4a6f5ae6
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/CountTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2019 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 CountTest : TestBase() {
+ @Test
+ fun testCount() = runTest {
+ val flow = flowOf(239, 240)
+ assertEquals(2, flow.count())
+ assertEquals(2, flow.count { true })
+ assertEquals(1, flow.count { it % 2 == 0})
+ assertEquals(0, flow.count { false })
+ }
+
+ @Test
+ fun testNoValues() = runTest {
+ assertEquals(0, flowOf<Int>().count())
+ assertEquals(0, flowOf<Int>().count { false })
+ assertEquals(0, flowOf<Int>().count { true })
+ }
+
+ @Test
+ fun testException() = runTest {
+ val flow = flow<Int> {
+ throw TestException()
+ }
+
+ assertFailsWith<TestException> { flow.count() }
+ assertFailsWith<TestException> { flow.count { false } }
+ }
+
+ @Test
+ fun testExceptionAfterValue() = runTest {
+ val flow = flow {
+ emit(1)
+ throw TestException()
+ }
+
+ assertFailsWith<TestException> { flow.count() }
+ assertFailsWith<TestException> { flow.count { false } }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt
new file mode 100644
index 00000000..e84d4c7b
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class FirstTest : TestBase() {
+ @Test
+ fun testFirst() = runTest {
+ val flow = flowOf(1, 2, 3)
+ assertEquals(1, flow.first())
+ }
+
+ @Test
+ fun testNulls() = runTest {
+ val flow = flowOf(null, 1)
+ assertNull(flow.first())
+ assertNull(flow.first { it == null })
+ assertEquals(1, flow.first { it != null })
+ }
+
+ @Test
+ fun testFirstWithPredicate() = runTest {
+ val flow = flowOf(1, 2, 3)
+ assertEquals(1, flow.first { it > 0 })
+ assertEquals(2, flow.first { it > 1 })
+ assertFailsWith<NoSuchElementException> { flow.first { it > 3 } }
+ }
+
+ @Test
+ fun testFirstCancellation() = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ hang { expect(1) }
+ }
+ emit(1)
+ emit(2)
+ }
+ }
+
+
+ val result = flow.first {
+ latch.receive()
+ true
+ }
+ assertEquals(1, result)
+ finish(2)
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ assertFailsWith<NoSuchElementException> { emptyFlow<Int>().first() }
+ assertFailsWith<NoSuchElementException> { emptyFlow<Int>().first { true } }
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ hang { expect(1) }
+ }
+ emit(1)
+ }
+ }
+
+ assertFailsWith<TestException> {
+ flow.first {
+ latch.receive()
+ throw TestException()
+ }
+ }
+
+ assertEquals(1, flow.first())
+ finish(2)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt
new file mode 100644
index 00000000..3c885713
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class FoldTest : TestBase() {
+ @Test
+ fun testFold() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ val result = flow.fold(3) { value, acc -> value + acc }
+ assertEquals(6, result)
+ }
+
+ @Test
+ fun testEmptyFold() = runTest {
+ val flow = flowOf<Int>()
+ assertEquals(42, flow.fold(42) { value, acc -> value + acc })
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ expect(3)
+ hang { expect(5) }
+ }
+ expect(2)
+ emit(1)
+ }
+ }
+
+ expect(1)
+ assertFailsWith<TestException> {
+ flow.fold(42) { _, _ ->
+ 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/LaunchFlow.kt b/kotlinx-coroutines-core/common/test/flow/terminal/LaunchFlow.kt
new file mode 100644
index 00000000..ceefa739
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/LaunchFlow.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016-2019 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.jvm.*
+import kotlin.reflect.*
+
+public typealias Handler<T> = suspend CoroutineScope.(T) -> Unit
+
+/*
+ * Design of this builder is not yet stable, so leaving it as is.
+ */
+public class LaunchFlowBuilder<T> {
+ /*
+ * NB: this implementation is a temporary ad-hoc (and slightly incorrect)
+ * solution until coroutine-builders are ready
+ *
+ * NB 2: this internal stuff is required to workaround KT-30795
+ */
+ @PublishedApi
+ internal var onEach: Handler<T>? = null
+ @PublishedApi
+ internal var finally: Handler<Throwable?>? = null
+ @PublishedApi
+ internal var exceptionHandlers = LinkedHashMap<KClass<*>, Handler<Throwable>>()
+
+ public fun onEach(action: suspend CoroutineScope.(value: T) -> Unit) {
+ check(onEach == null) { "onEach block is already registered" }
+ check(exceptionHandlers.isEmpty()) { "onEach block should be registered before exceptionHandlers block" }
+ check(finally == null) { "onEach block should be registered before finally block" }
+ onEach = action
+ }
+
+ public inline fun <reified T : Throwable> catch(noinline action: suspend CoroutineScope.(T) -> Unit) {
+ check(onEach != null) { "onEach block should be registered first" }
+ check(finally == null) { "exceptionHandlers block should be registered before finally block" }
+ @Suppress("UNCHECKED_CAST")
+ exceptionHandlers[T::class] = action as Handler<Throwable>
+ }
+
+ public fun finally(action: suspend CoroutineScope.(cause: Throwable?) -> Unit) {
+ check(finally == null) { "Finally block is already registered" }
+ check(onEach != null) { "onEach block should be registered before finally block" }
+ if (finally == null) finally = action
+ }
+
+ internal fun build(): Handlers<T> =
+ Handlers(onEach ?: error("onEach is not registered"), exceptionHandlers, finally)
+}
+
+internal class Handlers<T>(
+ @JvmField
+ internal var onEach: Handler<T>,
+ @JvmField
+ internal var exceptionHandlers: Map<KClass<*>, Handler<Throwable>>,
+ @JvmField
+ internal var finally: Handler<Throwable?>?
+)
+
+private fun <T> CoroutineScope.launchFlow(
+ flow: Flow<T>,
+ builder: LaunchFlowBuilder<T>.() -> Unit
+): Job {
+ val handlers = LaunchFlowBuilder<T>().apply(builder).build()
+ return launch {
+ var caught: Throwable? = null
+ try {
+ coroutineScope {
+ flow.collect { value ->
+ handlers.onEach(this, value)
+ }
+ }
+ } catch (e: Throwable) {
+ handlers.exceptionHandlers.forEach { (key, value) ->
+ if (key.isInstance(e)) {
+ caught = e
+ value.invoke(this, e)
+ return@forEach
+ }
+ }
+ if (caught == null) {
+ caught = e
+ throw e
+ }
+ } finally {
+ cancel() // TODO discuss
+ handlers.finally?.invoke(CoroutineScope(coroutineContext + NonCancellable), caught)
+ }
+ }
+}
+
+public fun <T> Flow<T>.launchIn(
+ scope: CoroutineScope,
+ builder: LaunchFlowBuilder<T>.() -> Unit
+): Job = scope.launchFlow(this, builder)
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/LaunchInTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/LaunchInTest.kt
new file mode 100644
index 00000000..6b04b024
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/LaunchInTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2019 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 LaunchInTest : TestBase() {
+
+ @Test
+ fun testLaunchIn() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ throw TestException()
+ }.onEach {
+ assertEquals(1, it)
+ expect(2)
+ }.onCompletion {
+ assertTrue(it is TestException)
+ expect(3)
+ }.catch {
+ assertTrue { it is TestException }
+ expect(4)
+ }
+
+ flow.launchIn(this).join()
+ finish(5)
+ }
+
+ @Test
+ fun testDispatcher() = runTest {
+ flow {
+ assertEquals("flow", NamedDispatchers.name())
+ emit(1)
+ expect(1)
+ }.launchIn(this + NamedDispatchers("flow")).join()
+ finish(2)
+ }
+
+ @Test
+ fun testUnhandledError() = runTest(expected = { it is TestException }) {
+ flow {
+ emit(1)
+ expect(1)
+ }.catch {
+ expectUnreached()
+ }.onCompletion {
+ finish(2)
+ throw TestException()
+ }.launchIn(this)
+ }
+
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt
new file mode 100644
index 00000000..a84c0278
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-2019 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 kotlin.test.*
+
+class ReduceTest : TestBase() {
+ @Test
+ fun testReduce() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ val result = flow.reduce { value, acc -> value + acc }
+ assertEquals(3, result)
+ }
+
+ @Test
+ fun testEmptyReduce() = runTest {
+ val flow = emptyFlow<Int>()
+ assertFailsWith<UnsupportedOperationException> { flow.reduce { acc, value -> value + acc } }
+ }
+
+ @Test
+ fun testNullableReduce() = runTest {
+ val flow = flowOf(1, null, null, 2)
+ var invocations = 0
+ val sum = flow.reduce { acc, value ->
+ ++invocations
+ value
+ }
+ assertEquals(2, sum)
+ assertEquals(3, invocations)
+ }
+
+ @Test
+ fun testReduceNulls() = runTest {
+ assertNull(flowOf(null).reduce { _, value -> value })
+ assertNull(flowOf(null, null).reduce { _, value -> value })
+ assertFailsWith<UnsupportedOperationException> { flowOf<Nothing?>().reduce { _, value -> value } }
+ }
+
+ @Test
+ fun testErrorCancelsUpstream() = runTest {
+ val latch = Channel<Unit>()
+ val flow = flow {
+ coroutineScope {
+ launch {
+ latch.send(Unit)
+ expect(3)
+ hang { expect(5) }
+ }
+ expect(2)
+ emit(1)
+ emit(2)
+ }
+ }
+
+ expect(1)
+ assertFailsWith<TestException> {
+ flow.reduce { _, _ ->
+ 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/SingleTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/SingleTest.kt
new file mode 100644
index 00000000..f7205957
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/SingleTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-2019 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 SingleTest : TestBase() {
+
+ @Test
+ fun testSingle() = runTest {
+ val flow = flow {
+ emit(239L)
+ }
+
+ assertEquals(239L, flow.single())
+ assertEquals(239L, flow.singleOrNull())
+
+ }
+
+ @Test
+ fun testMultipleValues() = runTest {
+ val flow = flow {
+ emit(239L)
+ emit(240L)
+ }
+ assertFailsWith<RuntimeException> { flow.single() }
+ assertFailsWith<RuntimeException> { flow.singleOrNull() }
+ }
+
+ @Test
+ fun testNoValues() = runTest {
+ assertFailsWith<NoSuchElementException> { flow<Int> {}.single() }
+ assertNull(flow<Int> {}.singleOrNull())
+ }
+
+ @Test
+ fun testException() = runTest {
+ val flow = flow<Int> {
+ throw TestException()
+ }
+
+ assertFailsWith<TestException> { flow.single() }
+ assertFailsWith<TestException> { flow.singleOrNull() }
+ }
+
+ @Test
+ fun testExceptionAfterValue() = runTest {
+ val flow = flow {
+ emit(1)
+ throw TestException()
+ }
+
+ assertFailsWith<TestException> { flow.single() }
+ assertFailsWith<TestException> { flow.singleOrNull() }
+ }
+
+ @Test
+ fun testNullableSingle() = runTest {
+ assertEquals(1, flowOf<Int?>(1).single())
+ assertNull(flowOf<Int?>(null).single())
+ assertFailsWith<NoSuchElementException> { flowOf<Int?>().single() }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/ToCollectionTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/ToCollectionTest.kt
new file mode 100644
index 00000000..cfcbb521
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/ToCollectionTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2019 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 ToCollectionTest : TestBase() {
+
+ private val flow = flow {
+ repeat(10) {
+ emit(42)
+ }
+ }
+
+ private val emptyFlow = flowOf<Int>()
+
+ @Test
+ fun testToList() = runTest {
+ assertEquals(List(10) { 42 }, flow.toList())
+ assertEquals(emptyList(), emptyFlow.toList())
+ }
+
+ @Test
+ fun testToSet() = runTest {
+ assertEquals(setOf(42), flow.toSet())
+ assertEquals(emptySet(), emptyFlow.toSet())
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt
new file mode 100644
index 00000000..ece95db1
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt
@@ -0,0 +1,391 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.intrinsics.*
+import kotlin.test.*
+
+class SelectArrayChannelTest : TestBase() {
+
+ @Test
+ fun testSelectSendSuccess() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(2)
+ assertEquals("OK", channel.receive())
+ finish(6)
+ }
+ yield() // to launched coroutine
+ expect(3)
+ select<Unit> {
+ channel.onSend("OK") {
+ expect(4)
+ }
+ }
+ expect(5)
+ }
+
+ @Test
+ fun testSelectSendSuccessWithDefault() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(2)
+ assertEquals("OK", channel.receive())
+ finish(6)
+ }
+ yield() // to launched coroutine
+ expect(3)
+ select<Unit> {
+ channel.onSend("OK") {
+ expect(4)
+ }
+ default {
+ expectUnreached()
+ }
+ }
+ expect(5)
+ }
+
+ @Test
+ fun testSelectSendReceiveBuf() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ select<Unit> {
+ channel.onSend("OK") {
+ expect(2)
+ }
+ }
+ expect(3)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(4)
+ assertEquals("OK", v)
+ }
+ }
+ finish(5)
+ }
+
+ @Test
+ fun testSelectSendWait() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(4)
+ assertEquals("BUF", channel.receive())
+ expect(5)
+ assertEquals("OK", channel.receive())
+ expect(6)
+ }
+ expect(2)
+ channel.send("BUF")
+ expect(3)
+ select<Unit> {
+ channel.onSend("OK") {
+ expect(7)
+ }
+ }
+ finish(8)
+ }
+
+ @Test
+ fun testSelectReceiveSuccess() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ channel.send("OK")
+ expect(2)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(3)
+ assertEquals("OK", v)
+ }
+ }
+ finish(4)
+ }
+
+ @Test
+ fun testSelectReceiveSuccessWithDefault() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ channel.send("OK")
+ expect(2)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(3)
+ assertEquals("OK", v)
+ }
+ default {
+ expectUnreached()
+ }
+ }
+ finish(4)
+ }
+
+ @Test
+ fun testSelectReceiveWaitWithDefault() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ select<Unit> {
+ channel.onReceive {
+ expectUnreached()
+ }
+ default {
+ expect(2)
+ }
+ }
+ expect(3)
+ channel.send("BUF")
+ expect(4)
+ // make sure second send blocks (select above is over)
+ launch {
+ expect(6)
+ channel.send("CHK")
+ finish(10)
+ }
+ expect(5)
+ yield()
+ expect(7)
+ assertEquals("BUF", channel.receive())
+ expect(8)
+ assertEquals("CHK", channel.receive())
+ expect(9)
+ }
+
+ @Test
+ fun testSelectReceiveWait() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(3)
+ channel.send("OK")
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(5)
+ assertEquals("OK", v)
+ }
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveClosed() = runTest({it is ClosedReceiveChannelException}) {
+ expect(1)
+ val channel = Channel<String>(1)
+ channel.close()
+ finish(2)
+ select<Unit> {
+ channel.onReceive {
+ expectUnreached()
+ }
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testSelectReceiveWaitClosed() = runTest({it is ClosedReceiveChannelException}) {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(3)
+ channel.close()
+ finish(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceive {
+ expectUnreached()
+ }
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testSelectSendResourceCleanup() = runTest {
+ val channel = Channel<Int>(1)
+ val n = 1000
+ expect(1)
+ channel.send(-1) // fill the buffer, so all subsequent sends cannot proceed
+ repeat(n) { i ->
+ select {
+ channel.onSend(i) { expectUnreached() }
+ default { expect(i + 2) }
+ }
+ }
+ finish(n + 2)
+ }
+
+ @Test
+ fun testSelectReceiveResourceCleanup() = runTest {
+ val channel = Channel<Int>(1)
+ val n = 1000
+ expect(1)
+ repeat(n) { i ->
+ select {
+ channel.onReceive { expectUnreached() }
+ default { expect(i + 2) }
+ }
+ }
+ finish(n + 2)
+ }
+
+ @Test
+ fun testSelectReceiveDispatchNonSuspending() = runTest {
+ val channel = Channel<Int>(1)
+ expect(1)
+ channel.send(42)
+ expect(2)
+ launch {
+ expect(4)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(5)
+ assertEquals(42, v)
+ expect(6)
+ }
+ }
+ expect(7) // returns from select without further dispatch
+ }
+ expect(3)
+ yield() // to launched
+ finish(8)
+ }
+
+ @Test
+ fun testSelectReceiveDispatchNonSuspending2() = runTest {
+ val channel = Channel<Int>(1)
+ expect(1)
+ channel.send(42)
+ expect(2)
+ launch {
+ expect(4)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(5)
+ assertEquals(42, v)
+ expect(6)
+ yield() // back to main
+ expect(8)
+ }
+ }
+ expect(9) // returns from select without further dispatch
+ }
+ expect(3)
+ yield() // to launched
+ expect(7)
+ yield() // again
+ finish(10)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedWaitClosed() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(3)
+ channel.close()
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceiveOrClosed {
+ expect(5)
+ assertTrue(it.isClosed)
+ assertNull(it.closeCause)
+ }
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedWaitClosedWithCause() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(3)
+ channel.close(TestException())
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceiveOrClosed {
+ expect(5)
+ assertTrue(it.isClosed)
+ assertTrue(it.closeCause is TestException)
+ }
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosed() = runTest {
+ val c = Channel<Int>(1)
+ val iterations = 10
+ expect(1)
+ val job = launch {
+ repeat(iterations) {
+ select<Unit> {
+ c.onReceiveOrClosed { v ->
+ expect(4 + it * 2)
+ assertEquals(it, v.value)
+ }
+ }
+ }
+ }
+
+ expect(2)
+ repeat(iterations) {
+ expect(3 + it * 2)
+ c.send(it)
+ yield()
+ }
+
+ job.join()
+ finish(3 + iterations * 2)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedDispatch() = runTest {
+ val c = Channel<Int>(1)
+ expect(1)
+ launch {
+ expect(3)
+ val res = select<String> {
+ c.onReceiveOrClosed { v ->
+ expect(6)
+ assertEquals(42, v.value)
+ yield() // back to main
+ expect(8)
+ "OK"
+ }
+ }
+ expect(9)
+ assertEquals("OK", res)
+ }
+ expect(2)
+ yield() // to launch
+ expect(4)
+ c.send(42) // do not suspend
+ expect(5)
+ yield() // to receive
+ expect(7)
+ yield() // again
+ finish(10)
+ }
+
+ // only for debugging
+ internal fun <R> SelectBuilder<R>.default(block: suspend () -> R) {
+ this as SelectBuilderImpl // type assertion
+ if (!trySelect(null)) return
+ block.startCoroutineUnintercepted(this)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectBiasTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectBiasTest.kt
new file mode 100644
index 00000000..ec88fb5e
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectBiasTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class SelectBiasTest : TestBase() {
+ val n = 10_000
+
+ @Test
+ fun testBiased() = runTest {
+ val d0 = async { 0 }
+ val d1 = async { 1 }
+ val counter = IntArray(2)
+ repeat(n) {
+ val selected = select<Int> {
+ d0.onAwait { 0 }
+ d1.onAwait { 1 }
+ }
+ counter[selected]++
+ }
+ assertEquals(n, counter[0])
+ assertEquals(0, counter[1])
+ }
+
+ @Test
+ fun testUnbiased() = runTest {
+ val d0 = async { 0 }
+ val d1 = async { 1 }
+ val counter = IntArray(2)
+ repeat(n) {
+ val selected = selectUnbiased<Int> {
+ d0.onAwait { 0 }
+ d1.onAwait { 1 }
+ }
+ counter[selected]++
+ }
+ assertTrue(counter[0] >= n / 4)
+ assertTrue(counter[1] >= n / 4)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectBuilderImplTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectBuilderImplTest.kt
new file mode 100644
index 00000000..d2311359
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectBuilderImplTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.test.*
+
+class SelectBuilderImplTest : TestBase() {
+ @Test
+ fun testIdempotentSelectResumeInline() {
+ var resumed = false
+ val delegate = object : Continuation<String> {
+ override val context: CoroutineContext get() = EmptyCoroutineContext
+ override fun resumeWith(result: Result<String>) {
+ check(result.getOrNull() == "OK")
+ resumed = true
+ }
+ }
+ val c = SelectBuilderImpl(delegate)
+ // still running builder
+ check(!c.isSelected)
+ check(c.trySelect("SELECT"))
+ check(c.isSelected)
+ check(!c.trySelect("OTHER"))
+ check(c.trySelect("SELECT"))
+ c.completion.resume("OK")
+ check(!resumed) // still running builder, didn't invoke delegate
+ check(c.isSelected)
+ check(!c.trySelect("OTHER"))
+ check(c.trySelect("SELECT"))
+ check(c.getResult() === "OK") // then builder returns
+ }
+
+ @Test
+ fun testIdempotentSelectResumeSuspended() {
+ var resumed = false
+ val delegate = object : Continuation<String> {
+ override val context: CoroutineContext get() = EmptyCoroutineContext
+ override fun resumeWith(result: Result<String>) {
+ check(result.getOrNull() == "OK")
+ resumed = true
+ }
+ }
+ val c = SelectBuilderImpl(delegate)
+ check(c.getResult() === COROUTINE_SUSPENDED) // suspend first
+ check(!c.isSelected)
+ check(c.trySelect("SELECT"))
+ check(c.isSelected)
+ check(!c.trySelect("OTHER"))
+ check(c.trySelect("SELECT"))
+ check(!resumed)
+ c.completion.resume("OK")
+ check(resumed)
+ check(c.isSelected)
+ check(!c.trySelect("OTHER"))
+ check(c.trySelect("SELECT"))
+ }
+
+ @Test
+ fun testIdempotentSelectResumeWithExceptionInline() {
+ var resumed = false
+ val delegate = object : Continuation<String> {
+ override val context: CoroutineContext get() = EmptyCoroutineContext
+ override fun resumeWith(result: Result<String>) {
+ check(result.exceptionOrNull() is TestException)
+ resumed = true
+ }
+ }
+ val c = SelectBuilderImpl(delegate)
+ // still running builder
+ check(!c.isSelected)
+ check(c.trySelect("SELECT"))
+ check(c.isSelected)
+ check(!c.trySelect("OTHER"))
+ check(c.trySelect("SELECT"))
+ c.completion.resumeWithException(TestException())
+ check(!resumed) // still running builder, didn't invoke delegate
+ check(c.isSelected)
+ check(!c.trySelect("OTHER"))
+ check(c.trySelect("SELECT"))
+ try {
+ c.getResult() // the builder should throw exception
+ error("Failed")
+ } catch (e: Throwable) {
+ check(e is TestException)
+ }
+ }
+
+ @Test
+ fun testIdempotentSelectResumeWithExceptionSuspended() {
+ var resumed = false
+ val delegate = object : Continuation<String> {
+ override val context: CoroutineContext get() = EmptyCoroutineContext
+ override fun resumeWith(result: Result<String>) {
+ check(result.exceptionOrNull() is TestException)
+ resumed = true
+ }
+ }
+ val c = SelectBuilderImpl(delegate)
+ check(c.getResult() === COROUTINE_SUSPENDED) // suspend first
+ check(!c.isSelected)
+ check(c.trySelect("SELECT"))
+ check(c.isSelected)
+ check(!c.trySelect("OTHER"))
+ check(c.trySelect("SELECT"))
+ check(!resumed)
+ c.completion.resumeWithException(TestException())
+ check(resumed)
+ check(c.isSelected)
+ check(!c.trySelect("OTHER"))
+ check(c.trySelect("SELECT"))
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectDeferredTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectDeferredTest.kt
new file mode 100644
index 00000000..b1c07bab
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectDeferredTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class SelectDeferredTest : TestBase() {
+ @Test
+ fun testSimpleReturnsImmediately() = runTest {
+ expect(1)
+ val d1 = async {
+ expect(3)
+ 42
+ }
+ expect(2)
+ val res = select<String> {
+ d1.onAwait { v ->
+ expect(4)
+ assertEquals(42, v)
+ "OK"
+ }
+ }
+ expect(5)
+ assertEquals("OK", res)
+ finish(6)
+ }
+
+ @Test
+ fun testSimpleWithYield() = runTest {
+ expect(1)
+ val d1 = async {
+ expect(3)
+ 42
+ }
+ launch {
+ expect(4)
+ yield() // back to main
+ expect(6)
+ }
+ expect(2)
+ val res = select<String> {
+ d1.onAwait { v ->
+ expect(5)
+ assertEquals(42, v)
+ yield() // to launch
+ expect(7)
+ "OK"
+ }
+ }
+ finish(8)
+ assertEquals("OK", res)
+ }
+
+ @Test
+ fun testSelectIncompleteLazy() = runTest {
+ expect(1)
+ val d1 = async(start = CoroutineStart.LAZY) {
+ expect(5)
+ 42
+ }
+ launch {
+ expect(3)
+ val res = select<String> {
+ d1.onAwait { v ->
+ expect(7)
+ assertEquals(42, v)
+ "OK"
+ }
+ }
+ expect(8)
+ assertEquals("OK", res)
+ }
+ expect(2)
+ yield() // to launch
+ expect(4)
+ yield() // to started async
+ expect(6)
+ yield() // to triggered select
+ finish(9)
+ }
+
+ @Test
+ fun testSelectTwo() = runTest {
+ expect(1)
+ val d1 = async {
+ expect(3)
+ yield() // to the other deffered
+ expect(5)
+ yield() // to fired select
+ expect(7)
+ "d1"
+ }
+ val d2 = async {
+ expect(4)
+ "d2" // returns result
+ }
+ expect(2)
+ val res = select<String> {
+ d1.onAwait {
+ expectUnreached()
+ "FAIL"
+ }
+ d2.onAwait { v2 ->
+ expect(6)
+ assertEquals("d2", v2)
+ yield() // to first deferred
+ expect(8)
+ "OK"
+ }
+ }
+ assertEquals("OK", res)
+ finish(9)
+ }
+
+ @Test
+ fun testSelectCancel() = runTest(
+ expected = { it is CancellationException }
+ ) {
+ expect(1)
+ val d = CompletableDeferred<String>()
+ launch {
+ finish(3)
+ d.cancel() // will cancel after select starts
+ }
+ expect(2)
+ select<Unit> {
+ d.onAwait {
+ expectUnreached() // will not select
+ }
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testSelectIncomplete() = runTest {
+ val deferred = async { Wrapper("OK") }
+ val result = select<Wrapper> {
+ assertFalse(deferred.isCompleted)
+ assertTrue(deferred.isActive)
+ deferred.onAwait {
+ it
+ }
+ }
+
+ assertEquals("OK", result.value)
+ }
+
+ @Test
+ fun testSelectIncompleteFastPath() = runTest {
+ val deferred = async(Dispatchers.Unconfined) { Wrapper("OK") }
+ val result = select<Wrapper> {
+ assertTrue(deferred.isCompleted)
+ assertFalse(deferred.isActive)
+ deferred.onAwait {
+ it
+ }
+ }
+
+ assertEquals("OK", result.value)
+ }
+
+ private class Wrapper(val value: String) : Incomplete {
+ override val isActive: Boolean
+ get() = error("")
+ override val list: NodeList?
+ get() = error("")
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectJobTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectJobTest.kt
new file mode 100644
index 00000000..099a8741
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectJobTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class SelectJobTest : TestBase() {
+ @Test
+ fun testSelectCompleted() = runTest {
+ expect(1)
+ launch { // makes sure we don't yield to it earlier
+ finish(4) // after main exits
+ }
+ val job = Job()
+ job.cancel()
+ select<Unit> {
+ job.onJoin {
+ expect(2)
+ }
+ }
+ expect(3)
+ // will wait for the first coroutine
+ }
+
+ @Test
+ fun testSelectIncomplete() = runTest {
+ expect(1)
+ val job = Job()
+ launch { // makes sure we don't yield to it earlier
+ expect(3)
+ val res = select<String> {
+ job.onJoin {
+ expect(6)
+ "OK"
+ }
+ }
+ expect(7)
+ assertEquals("OK", res)
+ }
+ expect(2)
+ yield()
+ expect(4)
+ job.cancel()
+ expect(5)
+ yield()
+ finish(8)
+ }
+
+ @Test
+ fun testSelectLazy() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.LAZY) {
+ expect(2)
+ }
+ val res = select<String> {
+ job.onJoin {
+ expect(3)
+ "OK"
+ }
+ }
+ finish(4)
+ assertEquals("OK", res)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectLinkedListChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectLinkedListChannelTest.kt
new file mode 100644
index 00000000..a066f6b3
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectLinkedListChannelTest.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlin.test.*
+
+class SelectLinkedListChannelTest : TestBase() {
+ @Test
+ fun testSelectSendWhenClosed() = runTest {
+ expect(1)
+ val c = Channel<Int>(Channel.UNLIMITED)
+ c.send(1) // enqueue buffered element
+ c.close() // then close
+ assertFailsWith<ClosedSendChannelException> {
+ // select sender should fail
+ expect(2)
+ select {
+ c.onSend(2) {
+ expectUnreached()
+ }
+ }
+ }
+ finish(3)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt
new file mode 100644
index 00000000..5af68f6b
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016-2019 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
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlin.test.*
+
+class SelectLoopTest : TestBase() {
+ // https://github.com/Kotlin/kotlinx.coroutines/issues/1130
+ @Test
+ fun testChannelSelectLoop() = runTest(
+ expected = { it is TestException }
+ ) {
+ expect(1)
+ val channel = Channel<Unit>()
+ val job = launch {
+ expect(2)
+ channel.send(Unit)
+ expect(3)
+ throw TestException()
+ }
+ var isDone = false
+ while (!isDone) {
+ select<Unit> {
+ channel.onReceiveOrNull {
+ expect(4)
+ assertEquals(Unit, it)
+ }
+ job.onJoin {
+ expect(5)
+ isDone = true
+ }
+ }
+ }
+ finish(6)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectMutexTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectMutexTest.kt
new file mode 100644
index 00000000..6f4c9e13
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectMutexTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.sync.*
+import kotlin.test.*
+
+class SelectMutexTest : TestBase() {
+ @Test
+ fun testSelectLock() = runTest {
+ val mutex = Mutex()
+ expect(1)
+ launch { // ensure that it is not scheduled earlier than needed
+ finish(4) // after main exits
+ }
+ val res = select<String> {
+ mutex.onLock {
+ assertTrue(mutex.isLocked)
+ expect(2)
+ "OK"
+ }
+ }
+ assertEquals("OK", res)
+ expect(3)
+ // will wait for the first coroutine
+ }
+
+ @Test
+ fun testSelectLockWait() = runTest {
+ val mutex = Mutex(true) // locked
+ expect(1)
+ launch {
+ expect(3)
+ val res = select<String> {
+ // will suspended
+ mutex.onLock {
+ assertTrue(mutex.isLocked)
+ expect(6)
+ "OK"
+ }
+ }
+ assertEquals("OK", res)
+ expect(7)
+ }
+ expect(2)
+ yield() // to launched coroutine
+ expect(4)
+ mutex.unlock()
+ expect(5)
+ yield() // to resumed select
+ finish(8)
+ }
+} \ 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
new file mode 100644
index 00000000..6072cc2c
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2016-2018 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
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.intrinsics.*
+import kotlin.test.*
+
+class SelectRendezvousChannelTest : TestBase() {
+
+ @Test
+ fun testSelectSendSuccess() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(2)
+ assertEquals("OK", channel.receive())
+ finish(6)
+ }
+ yield() // to launched coroutine
+ expect(3)
+ select<Unit> {
+ channel.onSend("OK") {
+ expect(4)
+ }
+ }
+ expect(5)
+ }
+
+ @Test
+ fun testSelectSendSuccessWithDefault() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(2)
+ assertEquals("OK", channel.receive())
+ finish(6)
+ }
+ yield() // to launched coroutine
+ expect(3)
+ select<Unit> {
+ channel.onSend("OK") {
+ expect(4)
+ }
+ default {
+ expectUnreached()
+ }
+ }
+ expect(5)
+ }
+
+ @Test
+ fun testSelectSendWaitWithDefault() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ select<Unit> {
+ channel.onSend("OK") {
+ expectUnreached()
+ }
+ default {
+ expect(2)
+ }
+ }
+ expect(3)
+ // make sure receive blocks (select above is over)
+ launch {
+ expect(5)
+ assertEquals("CHK", channel.receive())
+ finish(8)
+ }
+ expect(4)
+ yield()
+ expect(6)
+ channel.send("CHK")
+ expect(7)
+ }
+
+ @Test
+ fun testSelectSendWait() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(3)
+ assertEquals("OK", channel.receive())
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onSend("OK") {
+ expect(5)
+ }
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveSuccess() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(2)
+ channel.send("OK")
+ finish(6)
+ }
+ yield() // to launched coroutine
+ expect(3)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(4)
+ assertEquals("OK", v)
+ }
+ }
+ expect(5)
+ }
+
+ @Test
+ fun testSelectReceiveSuccessWithDefault() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(2)
+ channel.send("OK")
+ finish(6)
+ }
+ yield() // to launched coroutine
+ expect(3)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(4)
+ assertEquals("OK", v)
+ }
+ default {
+ expectUnreached()
+ }
+ }
+ expect(5)
+ }
+
+ @Test
+ fun testSelectReceiveWaitWithDefault() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ select<Unit> {
+ channel.onReceive {
+ expectUnreached()
+ }
+ default {
+ expect(2)
+ }
+ }
+ expect(3)
+ // make sure send blocks (select above is over)
+ launch {
+ expect(5)
+ channel.send("CHK")
+ finish(8)
+ }
+ expect(4)
+ yield()
+ expect(6)
+ assertEquals("CHK", channel.receive())
+ expect(7)
+ }
+
+ @Test
+ fun testSelectReceiveWait() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(3)
+ channel.send("OK")
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceive { v ->
+ expect(5)
+ assertEquals("OK", v)
+ }
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveClosed() = runTest(expected = { it is ClosedReceiveChannelException }) {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ channel.close()
+ finish(2)
+ select<Unit> {
+ channel.onReceive {
+ expectUnreached()
+ }
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testSelectReceiveWaitClosed() = runTest(expected = {it is ClosedReceiveChannelException}) {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(3)
+ channel.close()
+ finish(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceive {
+ expectUnreached()
+ }
+ }
+ expectUnreached()
+ }
+
+ @Test
+ fun testSelectSendResourceCleanup() = runTest {
+ val channel = Channel<Int>(Channel.RENDEZVOUS)
+ val n = 1_000
+ expect(1)
+ repeat(n) { i ->
+ select {
+ channel.onSend(i) { expectUnreached() }
+ default { expect(i + 2) }
+ }
+ }
+ finish(n + 2)
+ }
+
+ @Test
+ fun testSelectReceiveResourceCleanup() = runTest {
+ val channel = Channel<Int>(Channel.RENDEZVOUS)
+ val n = 1_000
+ expect(1)
+ repeat(n) { i ->
+ select {
+ channel.onReceive { expectUnreached() }
+ default { expect(i + 2) }
+ }
+ }
+ finish(n + 2)
+ }
+
+ @Test
+ fun testSelectAtomicFailure() = runTest {
+ val c1 = Channel<Int>(Channel.RENDEZVOUS)
+ val c2 = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ val res = select<String> {
+ c1.onReceive { v1 ->
+ expect(4)
+ assertEquals(42, v1)
+ yield() // back to main
+ expect(7)
+ "OK"
+ }
+ c2.onReceive {
+ "FAIL"
+ }
+ }
+ expect(8)
+ assertEquals("OK", res)
+ }
+ expect(2)
+ c1.send(42) // send to coroutine, suspends
+ expect(5)
+ c2.close() // makes sure that selected expression does not fail!
+ expect(6)
+ yield() // back
+ finish(9)
+ }
+
+ @Test
+ fun testSelectWaitDispatch() = runTest {
+ val c = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ val res = select<String> {
+ c.onReceive { v ->
+ expect(6)
+ assertEquals(42, v)
+ yield() // back to main
+ expect(8)
+ "OK"
+ }
+ }
+ expect(9)
+ assertEquals("OK", res)
+ }
+ expect(2)
+ yield() // to launch
+ expect(4)
+ c.send(42) // do not suspend
+ expect(5)
+ yield() // to receive
+ expect(7)
+ yield() // again
+ finish(10)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedWaitClosed() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(3)
+ channel.close()
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceiveOrClosed {
+ expect(5)
+ assertTrue(it.isClosed)
+ assertNull(it.closeCause)
+ }
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedWaitClosedWithCause() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(3)
+ channel.close(TestException())
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceiveOrClosed {
+ expect(5)
+ assertTrue(it.isClosed)
+ assertTrue(it.closeCause is TestException)
+ }
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosed() = runTest {
+ val channel = Channel<Int>(Channel.RENDEZVOUS)
+ val iterations = 10
+ expect(1)
+ val job = launch {
+ repeat(iterations) {
+ select<Unit> {
+ channel.onReceiveOrClosed { v ->
+ expect(4 + it * 2)
+ assertEquals(it, v.value)
+ }
+ }
+ }
+ }
+
+ expect(2)
+ repeat(iterations) {
+ expect(3 + it * 2)
+ channel.send(it)
+ yield()
+ }
+
+ job.join()
+ finish(3 + iterations * 2)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedDispatch() = runTest {
+ val c = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ val res = select<String> {
+ c.onReceiveOrClosed { v ->
+ expect(6)
+ assertEquals(42, v.value)
+ yield() // back to main
+ expect(8)
+ "OK"
+ }
+ }
+ expect(9)
+ assertEquals("OK", res)
+ }
+ expect(2)
+ yield() // to launch
+ expect(4)
+ c.send(42) // do not suspend
+ expect(5)
+ yield() // to receive
+ expect(7)
+ yield() // again
+ finish(10)
+ }
+
+ @Test
+ fun testSelectSendWhenClosed() = runTest {
+ expect(1)
+ val c = Channel<Int>(Channel.RENDEZVOUS)
+ val sender = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ c.send(1) // enqueue sender
+ expectUnreached()
+ }
+ c.close() // then close
+ assertFailsWith<ClosedSendChannelException> {
+ // select sender should fail
+ expect(3)
+ select {
+ c.onSend(2) {
+ expectUnreached()
+ }
+ }
+ }
+ sender.cancel()
+ finish(4)
+ }
+
+ // only for debugging
+ internal fun <R> SelectBuilder<R>.default(block: suspend () -> R) {
+ this as SelectBuilderImpl // type assertion
+ if (!trySelect(null)) return
+ block.startCoroutineUnintercepted(this)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectTimeoutTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutTest.kt
new file mode 100644
index 00000000..fbfb9c05
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class SelectTimeoutTest : TestBase() {
+ @Test
+ fun testBasic() = runTest {
+ expect(1)
+ val result = select<String> {
+ onTimeout(1000) {
+ expectUnreached()
+ "FAIL"
+ }
+ onTimeout(100) {
+ expect(2)
+ "OK"
+ }
+ onTimeout(500) {
+ expectUnreached()
+ "FAIL"
+ }
+ }
+ assertEquals("OK", result)
+ finish(3)
+ }
+
+ @Test
+ fun testZeroTimeout() = runTest {
+ expect(1)
+ val result = select<String> {
+ onTimeout(1000) {
+ expectUnreached()
+ "FAIL"
+ }
+ onTimeout(0) {
+ expect(2)
+ "OK"
+ }
+ }
+ assertEquals("OK", result)
+ finish(3)
+ }
+
+ @Test
+ fun testNegativeTimeout() = runTest {
+ expect(1)
+ val result = select<String> {
+ onTimeout(1000) {
+ expectUnreached()
+ "FAIL"
+ }
+ onTimeout(-10) {
+ expect(2)
+ "OK"
+ }
+ }
+ assertEquals("OK", result)
+ finish(3)
+ }
+
+ @Test
+ fun testUnbiasedNegativeTimeout() = runTest {
+ val counters = intArrayOf(0, 0, 0)
+ val iterations =10_000
+ for (i in 0..iterations) {
+ val result = selectUnbiased<Int> {
+ onTimeout(-1000) {
+ 0
+ }
+ onTimeout(0) {
+ 1
+ }
+ onTimeout(1000) {
+ expectUnreached()
+ 2
+ }
+ }
+ ++counters[result]
+ }
+ assertEquals(0, counters[2])
+ assertTrue { counters[0] > iterations / 4 }
+ assertTrue { counters[1] > iterations / 4 }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt
new file mode 100644
index 00000000..c5d0ccf1
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.sync
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class MutexTest : TestBase() {
+ @Test
+ fun testSimple() = runTest {
+ val mutex = Mutex()
+ expect(1)
+ launch {
+ expect(4)
+ mutex.lock() // suspends
+ expect(7) // now got lock
+ mutex.unlock()
+ expect(8)
+ }
+ expect(2)
+ mutex.lock() // locked
+ expect(3)
+ yield() // yield to child
+ expect(5)
+ mutex.unlock()
+ expect(6)
+ yield() // now child has lock
+ finish(9)
+ }
+
+ @Test
+ fun tryLockTest() {
+ val mutex = Mutex()
+ assertFalse(mutex.isLocked)
+ assertTrue(mutex.tryLock())
+ assertTrue(mutex.isLocked)
+ assertFalse(mutex.tryLock())
+ assertTrue(mutex.isLocked)
+ mutex.unlock()
+ assertFalse(mutex.isLocked)
+ assertTrue(mutex.tryLock())
+ assertTrue(mutex.isLocked)
+ assertFalse(mutex.tryLock())
+ assertTrue(mutex.isLocked)
+ mutex.unlock()
+ assertFalse(mutex.isLocked)
+ }
+
+ @Test
+ fun withLockTest() = runTest {
+ val mutex = Mutex()
+ assertFalse(mutex.isLocked)
+ mutex.withLock {
+ assertTrue(mutex.isLocked)
+ }
+ assertFalse(mutex.isLocked)
+ }
+
+ @Test
+ fun testUnconfinedStackOverflow() {
+ val waiters = 10000
+ val mutex = Mutex(true)
+ var done = 0
+ repeat(waiters) {
+ GlobalScope.launch(Dispatchers.Unconfined) { // a lot of unconfined waiters
+ mutex.withLock {
+ done++
+ }
+ }
+ }
+ mutex.unlock() // should not produce StackOverflowError
+ assertEquals(waiters, done)
+ }
+
+ @Test
+ fun holdLock() = runTest {
+ val mutex = Mutex()
+ val firstOwner = Any()
+ val secondOwner = Any()
+
+ // no lock
+ assertFalse(mutex.holdsLock(firstOwner))
+ assertFalse(mutex.holdsLock(secondOwner))
+
+ // owner firstOwner
+ mutex.lock(firstOwner)
+ val secondLockJob = launch {
+ mutex.lock(secondOwner)
+ }
+
+ assertTrue(mutex.holdsLock(firstOwner))
+ assertFalse(mutex.holdsLock(secondOwner))
+
+ // owner secondOwner
+ mutex.unlock(firstOwner)
+ secondLockJob.join()
+
+ assertFalse(mutex.holdsLock(firstOwner))
+ assertTrue(mutex.holdsLock(secondOwner))
+
+ mutex.unlock(secondOwner)
+
+ // no lock
+ assertFalse(mutex.holdsLock(firstOwner))
+ assertFalse(mutex.holdsLock(secondOwner))
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt b/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt
new file mode 100644
index 00000000..b4ff88b8
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt
@@ -0,0 +1,171 @@
+package kotlinx.coroutines.sync
+
+import kotlinx.coroutines.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class SemaphoreTest : TestBase() {
+
+ @Test
+ fun testSimple() = runTest {
+ val semaphore = Semaphore(2)
+ launch {
+ expect(3)
+ semaphore.release()
+ expect(4)
+ }
+ expect(1)
+ semaphore.acquire()
+ semaphore.acquire()
+ expect(2)
+ semaphore.acquire()
+ finish(5)
+ }
+
+ @Test
+ fun testSimpleAsMutex() = runTest {
+ val semaphore = Semaphore(1)
+ expect(1)
+ launch {
+ expect(4)
+ semaphore.acquire() // suspends
+ expect(7) // now got lock
+ semaphore.release()
+ expect(8)
+ }
+ expect(2)
+ semaphore.acquire() // locked
+ expect(3)
+ yield() // yield to child
+ expect(5)
+ semaphore.release()
+ expect(6)
+ yield() // now child has lock
+ finish(9)
+ }
+
+ @Test
+ fun tryAcquireTest() = runTest {
+ val semaphore = Semaphore(2)
+ assertTrue(semaphore.tryAcquire())
+ assertTrue(semaphore.tryAcquire())
+ assertFalse(semaphore.tryAcquire())
+ assertEquals(0, semaphore.availablePermits)
+ semaphore.release()
+ assertEquals(1, semaphore.availablePermits)
+ assertTrue(semaphore.tryAcquire())
+ assertEquals(0, semaphore.availablePermits)
+ }
+
+ @Test
+ fun withSemaphoreTest() = runTest {
+ val semaphore = Semaphore(1)
+ assertEquals(1, semaphore.availablePermits)
+ semaphore.withPermit {
+ assertEquals(0, semaphore.availablePermits)
+ }
+ assertEquals(1, semaphore.availablePermits)
+ }
+
+ @Test
+ fun fairnessTest() = runTest {
+ val semaphore = Semaphore(1)
+ semaphore.acquire()
+ launch(coroutineContext) {
+ // first to acquire
+ expect(2)
+ semaphore.acquire() // suspend
+ expect(6)
+ }
+ launch(coroutineContext) {
+ // second to acquire
+ expect(3)
+ semaphore.acquire() // suspend
+ expect(9)
+ }
+ expect(1)
+ yield()
+ expect(4)
+ semaphore.release()
+ expect(5)
+ yield()
+ expect(7)
+ semaphore.release()
+ expect(8)
+ yield()
+ finish(10)
+ }
+
+ @Test
+ fun testCancellationReturnsPermitBack() = runTest {
+ val semaphore = Semaphore(1)
+ semaphore.acquire()
+ assertEquals(0, semaphore.availablePermits)
+ val job = launch {
+ assertFalse(semaphore.tryAcquire())
+ semaphore.acquire()
+ }
+ yield()
+ job.cancelAndJoin()
+ assertEquals(0, semaphore.availablePermits)
+ semaphore.release()
+ assertEquals(1, semaphore.availablePermits)
+ }
+
+ @Test
+ fun testCancellationDoesNotResumeWaitingAcquirers() = runTest {
+ val semaphore = Semaphore(1)
+ semaphore.acquire()
+ val job1 = launch { // 1st job in the waiting queue
+ expect(2)
+ semaphore.acquire()
+ expectUnreached()
+ }
+ val job2 = launch { // 2nd job in the waiting queue
+ expect(3)
+ semaphore.acquire()
+ expectUnreached()
+ }
+ expect(1)
+ yield()
+ expect(4)
+ job2.cancel()
+ yield()
+ expect(5)
+ job1.cancel()
+ finish(6)
+ }
+
+ @Test
+ fun testAcquiredPermits() = runTest {
+ val semaphore = Semaphore(5, acquiredPermits = 4)
+ assertEquals(semaphore.availablePermits, 1)
+ semaphore.acquire()
+ assertEquals(semaphore.availablePermits, 0)
+ assertFalse(semaphore.tryAcquire())
+ semaphore.release()
+ assertEquals(semaphore.availablePermits, 1)
+ assertTrue(semaphore.tryAcquire())
+ }
+
+ @Test
+ fun testReleaseAcquiredPermits() = runTest {
+ val semaphore = Semaphore(5, acquiredPermits = 4)
+ assertEquals(semaphore.availablePermits, 1)
+ repeat(4) { semaphore.release() }
+ assertEquals(5, semaphore.availablePermits)
+ assertFailsWith<IllegalStateException> { semaphore.release() }
+ repeat(5) { assertTrue(semaphore.tryAcquire()) }
+ assertFalse(semaphore.tryAcquire())
+ }
+
+ @Test
+ fun testIllegalArguments() {
+ assertFailsWith<IllegalArgumentException> { Semaphore(-1, 0) }
+ assertFailsWith<IllegalArgumentException> { Semaphore(0, 0) }
+ assertFailsWith<IllegalArgumentException> { Semaphore(1, -1) }
+ assertFailsWith<IllegalArgumentException> { Semaphore(1, 2) }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/npm/README.md b/kotlinx-coroutines-core/js/npm/README.md
new file mode 100644
index 00000000..7f88ea39
--- /dev/null
+++ b/kotlinx-coroutines-core/js/npm/README.md
@@ -0,0 +1,19 @@
+# kotlinx.coroutines
+
+Library support for Kotlin coroutines in
+[Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html).
+
+```kotlin
+suspend fun main() = coroutineScope {
+ launch {
+ delay(1000)
+ println("Kotlin Coroutines World!")
+ }
+ println("Hello")
+}
+```
+
+## Documentation
+
+* [Guide to kotlinx.coroutines by example on JVM](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html) (**read it first**)
+* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines)
diff --git a/kotlinx-coroutines-core/js/npm/package.json b/kotlinx-coroutines-core/js/npm/package.json
new file mode 100644
index 00000000..5dda3943
--- /dev/null
+++ b/kotlinx-coroutines-core/js/npm/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "kotlinx-coroutines-core",
+ "version" : "$version",
+ "description" : "Library support for Kotlin coroutines",
+ "main" : "kotlinx-coroutines-core.js",
+ "author": "JetBrains",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/Kotlin/kotlinx.coroutines",
+ "bugs": {
+ "url": "https://github.com/Kotlin/kotlinx.coroutines/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Kotlin/kotlinx.coroutines.git"
+ },
+ "keywords": [
+ "Kotlin",
+ "async",
+ "coroutines",
+ "JavaScript",
+ "JetBrains"
+ ],
+ "peerDependencies": {
+ $kotlinDependency
+ }
+}
diff --git a/kotlinx-coroutines-core/js/src/CompletionHandler.kt b/kotlinx-coroutines-core/js/src/CompletionHandler.kt
new file mode 100644
index 00000000..19a9be88
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/CompletionHandler.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+
+internal actual abstract class CompletionHandlerBase : LinkedListNode() {
+ @JsName("invoke")
+ actual abstract fun invoke(cause: Throwable?)
+}
+
+@Suppress("UnsafeCastFromDynamic")
+internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler get() = asDynamic()
+
+internal actual abstract class CancelHandlerBase {
+ @JsName("invoke")
+ actual abstract fun invoke(cause: Throwable?)
+}
+
+@Suppress("UnsafeCastFromDynamic")
+internal actual inline val CancelHandlerBase.asHandler: CompletionHandler get() = asDynamic()
+
+internal actual fun CompletionHandler.invokeIt(cause: Throwable?) {
+ when(jsTypeOf(this)) {
+ "function" -> invoke(cause)
+ else -> asDynamic().invoke(cause)
+ }
+}
diff --git a/kotlinx-coroutines-core/js/src/CoroutineContext.kt b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
new file mode 100644
index 00000000..3390fc1b
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.browser.*
+import kotlin.coroutines.*
+
+private external val navigator: dynamic
+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."
+ isJsdom() -> NodeDispatcher
+ // Check if we are in the browser and must use window.postMessage to avoid setTimeout throttling
+ 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
+ // Fallback to NodeDispatcher when browser environment is not detected
+ else -> NodeDispatcher
+}
+
+private fun isJsdom() = jsTypeOf(navigator) != UNDEFINED &&
+ navigator != null &&
+ navigator.userAgent != null &&
+ jsTypeOf(navigator.userAgent) != UNDEFINED &&
+ jsTypeOf(navigator.userAgent.match) != UNDEFINED &&
+ navigator.userAgent.match("\\bjsdom\\b")
+
+internal actual val DefaultDelay: Delay
+ get() = Dispatchers.Default as Delay
+
+public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
+ val combined = coroutineContext + context
+ return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
+ combined + Dispatchers.Default else combined
+}
+
+// No debugging facilities on JS
+internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, 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 \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt
new file mode 100644
index 00000000..4bc378d3
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
+ // log exception
+ console.error(exception)
+}
diff --git a/kotlinx-coroutines-core/js/src/Debug.kt b/kotlinx-coroutines-core/js/src/Debug.kt
new file mode 100644
index 00000000..57a94d43
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/Debug.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+private var counter = 0
+
+internal actual val DEBUG: Boolean = false
+
+internal actual val Any.hexAddress: String
+ get() {
+ var result = this.asDynamic().__debug_counter
+ if (jsTypeOf(result) !== "number") {
+ result = ++counter
+ this.asDynamic().__debug_counter = result
+
+ }
+ return (result as Int).toString()
+ }
+
+internal actual val Any.classSimpleName: String get() = this::class.simpleName ?: "Unknown"
+
+internal actual inline fun assert(value: () -> Boolean) {} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/Dispatchers.kt b/kotlinx-coroutines-core/js/src/Dispatchers.kt
new file mode 100644
index 00000000..3667f2e1
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/Dispatchers.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+public actual object Dispatchers {
+
+ public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
+
+ public actual val Main: MainCoroutineDispatcher = JsMainDispatcher(Default)
+
+ public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
+}
+
+private class JsMainDispatcher(val delegate: CoroutineDispatcher) : MainCoroutineDispatcher() {
+
+ override val immediate: MainCoroutineDispatcher
+ get() = throw UnsupportedOperationException("Immediate dispatching is not supported on JS")
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) = delegate.dispatch(context, block)
+
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean = delegate.isDispatchNeeded(context)
+
+ override fun dispatchYield(context: CoroutineContext, block: Runnable) = delegate.dispatchYield(context, block)
+
+ override fun toString(): String = delegate.toString()
+}
diff --git a/kotlinx-coroutines-core/js/src/EventLoop.kt b/kotlinx-coroutines-core/js/src/EventLoop.kt
new file mode 100644
index 00000000..19d75c09
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/EventLoop.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+internal actual fun createEventLoop(): EventLoop = UnconfinedEventLoop()
+
+internal actual fun nanoTime(): Long = unsupported()
+
+internal class UnconfinedEventLoop : EventLoop() {
+ override fun dispatch(context: CoroutineContext, block: Runnable): Unit = unsupported()
+}
+
+internal actual abstract class EventLoopImplPlatform : EventLoop() {
+ protected actual fun unpark(): Unit = unsupported()
+ protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask): Unit = unsupported()
+}
+
+internal actual object DefaultExecutor {
+ public actual fun enqueue(task: Runnable): Unit = unsupported()
+}
+
+private fun unsupported(): Nothing =
+ throw UnsupportedOperationException("runBlocking event loop is not supported")
diff --git a/kotlinx-coroutines-core/js/src/Exceptions.kt b/kotlinx-coroutines-core/js/src/Exceptions.kt
new file mode 100644
index 00000000..f4270410
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/Exceptions.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * This exception gets thrown if an exception is caught while processing [CompletionHandler] invocation for [Job].
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual class CompletionHandlerException public actual constructor(
+ message: String,
+ public override val cause: Throwable
+) : RuntimeException(message.withCause(cause))
+
+/**
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending.
+ * It indicates _normal_ cancellation of a coroutine.
+ * **It is not printed to console/log by default uncaught exception handler**.
+ * (see [CoroutineExceptionHandler]).
+ */
+public actual open class CancellationException actual constructor(message: String?) : IllegalStateException(message)
+
+/**
+ * Creates a cancellation exception with a specified message and [cause].
+ */
+@Suppress("FunctionName")
+public actual fun CancellationException(message: String?, cause: Throwable?) : CancellationException =
+ CancellationException(message.withCause(cause))
+
+/**
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed
+ * without cause, or with a cause or exception that is not [CancellationException]
+ * (see [Job.getCancellationException]).
+ */
+internal actual class JobCancellationException public actual constructor(
+ message: String,
+ public override val cause: Throwable?,
+ internal actual val job: Job
+) : CancellationException(message.withCause(cause)) {
+ override fun toString(): String = "${super.toString()}; job=$job"
+ override fun equals(other: Any?): Boolean =
+ other === this ||
+ other is JobCancellationException && other.message == message && other.job == job && other.cause == cause
+ override fun hashCode(): Int =
+ (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0)
+}
+
+@Suppress("FunctionName")
+internal fun IllegalStateException(message: String, cause: Throwable?) =
+ IllegalStateException(message.withCause(cause))
+
+private fun String?.withCause(cause: Throwable?) =
+ when {
+ cause == null -> this
+ this == null -> "caused by $cause"
+ else -> "$this; caused by $cause"
+ }
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) { /* empty */ }
+
+// For use in tests
+internal actual val RECOVER_STACK_TRACES: Boolean = false \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/JSDispatcher.kt b/kotlinx-coroutines-core/js/src/JSDispatcher.kt
new file mode 100644
index 00000000..5a85244d
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/JSDispatcher.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2016-2018 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.w3c.dom.*
+import kotlin.coroutines.*
+import kotlin.js.Promise
+
+private const val MAX_DELAY = Int.MAX_VALUE.toLong()
+
+private fun delayToInt(timeMillis: Long): Int =
+ timeMillis.coerceIn(0, MAX_DELAY).toInt()
+
+internal sealed class SetTimeoutBasedDispatcher: CoroutineDispatcher(), Delay {
+ inner class ScheduledMessageQueue : MessageQueue() {
+ internal val processQueue: dynamic = { process() }
+
+ override fun schedule() {
+ scheduleQueueProcessing()
+ }
+
+ override fun reschedule() {
+ setTimeout(processQueue, 0)
+ }
+ }
+
+ internal val messageQueue = ScheduledMessageQueue()
+
+ abstract fun scheduleQueueProcessing()
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ messageQueue.enqueue(block)
+ }
+
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val handle = setTimeout({ block.run() }, delayToInt(timeMillis))
+ return ClearTimeout(handle)
+ }
+
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis))
+ // Actually on cancellation, but clearTimeout is idempotent
+ continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler)
+ }
+}
+
+internal object NodeDispatcher : SetTimeoutBasedDispatcher() {
+ override fun scheduleQueueProcessing() {
+ process.nextTick(messageQueue.processQueue)
+ }
+}
+
+internal object SetTimeoutDispatcher : SetTimeoutBasedDispatcher() {
+ override fun scheduleQueueProcessing() {
+ setTimeout(messageQueue.processQueue, 0)
+ }
+}
+
+private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle {
+
+ override fun dispose() {
+ clearTimeout(handle)
+ }
+
+ override fun invoke(cause: Throwable?) {
+ dispose()
+ }
+
+ override fun toString(): String = "ClearTimeout[$handle]"
+}
+
+internal class WindowDispatcher(private val window: Window) : CoroutineDispatcher(), Delay {
+ private val queue = WindowMessageQueue(window)
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) = queue.enqueue(block)
+
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis))
+ }
+
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val handle = window.setTimeout({ block.run() }, delayToInt(timeMillis))
+ return object : DisposableHandle {
+ override fun dispose() {
+ window.clearTimeout(handle)
+ }
+ }
+ }
+}
+
+private class WindowMessageQueue(private val window: Window) : MessageQueue() {
+ private val messageName = "dispatchCoroutine"
+
+ init {
+ window.addEventListener("message", { event: dynamic ->
+ if (event.source == window && event.data == messageName) {
+ event.stopPropagation()
+ process()
+ }
+ }, true)
+ }
+
+ override fun schedule() {
+ Promise.resolve(Unit).then({ process() })
+ }
+
+ override fun reschedule() {
+ window.postMessage(messageName, "*")
+ }
+}
+
+/**
+ * An abstraction over JS scheduling mechanism that leverages micro-batching of [dispatch] blocks without
+ * paying the cost of JS callbacks scheduling on every dispatch.
+ *
+ * Queue uses two scheduling mechanisms:
+ * 1) [schedule] is used to schedule the initial processing of the message queue.
+ * JS engine-specific microtask mechanism is used in order to boost performance on short runs and a dispatch batch
+ * 2) [reschedule] is used to schedule processing of the queue after yield to the JS event loop.
+ * JS engine-specific macrotask mechanism is used not to starve animations and non-coroutines macrotasks.
+ *
+ * Yet there could be a long tail of "slow" reschedules, but it should be amortized by the queue size.
+ */
+internal abstract class MessageQueue : ArrayQueue<Runnable>() {
+ val yieldEvery = 16 // yield to JS macrotask event loop after this many processed messages
+ private var scheduled = false
+
+ abstract fun schedule()
+
+ abstract fun reschedule()
+
+ fun enqueue(element: Runnable) {
+ addLast(element)
+ if (!scheduled) {
+ scheduled = true
+ schedule()
+ }
+ }
+
+ fun process() {
+ try {
+ // limit number of processed messages
+ repeat(yieldEvery) {
+ val element = removeFirstOrNull() ?: return@process
+ element.run()
+ }
+ } finally {
+ if (isEmpty) {
+ scheduled = false
+ } else {
+ reschedule()
+ }
+ }
+ }
+}
+
+// We need to reference global setTimeout and clearTimeout so that it works on Node.JS as opposed to
+// using them via "window" (which only works in browser)
+private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int
+private external fun clearTimeout(handle: Int = definedExternally)
diff --git a/kotlinx-coroutines-core/js/src/Promise.kt b/kotlinx-coroutines-core/js/src/Promise.kt
new file mode 100644
index 00000000..b8177ad0
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/Promise.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.js.*
+
+/**
+ * Starts new coroutine and returns its result as an implementation of [Promise].
+ *
+ * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
+ * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
+ * with corresponding [coroutineContext] element.
+ *
+ * By default, the coroutine is immediately scheduled for execution.
+ * Other options can be specified via `start` parameter. See [CoroutineStart] for details.
+ *
+ * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the coroutine code.
+ */
+public fun <T> CoroutineScope.promise(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> T
+): Promise<T> =
+ async(context, start, block).asPromise()
+
+/**
+ * Converts this deferred value to the instance of [Promise].
+ */
+public fun <T> Deferred<T>.asPromise(): Promise<T> {
+ val promise = Promise<T> { resolve, reject ->
+ invokeOnCompletion {
+ val e = getCompletionExceptionOrNull()
+ if (e != null) {
+ reject(e)
+ } else {
+ resolve(getCompleted())
+ }
+ }
+ }
+ promise.asDynamic().deferred = this
+ return promise
+}
+
+/**
+ * Converts this promise value to the instance of [Deferred].
+ */
+public fun <T> Promise<T>.asDeferred(): Deferred<T> {
+ val deferred = asDynamic().deferred
+ @Suppress("UnsafeCastFromDynamic")
+ return deferred ?: GlobalScope.async(start = CoroutineStart.UNDISPATCHED) { await() }
+}
+
+/**
+ * Awaits for completion of the promise without blocking.
+ *
+ * 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 promise and immediately resumes with [CancellationException].
+ */
+public suspend fun <T> Promise<T>.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
+ this@await.then(
+ onFulfilled = { cont.resume(it) },
+ onRejected = { cont.resumeWithException(it) })
+}
diff --git a/kotlinx-coroutines-core/js/src/Runnable.kt b/kotlinx-coroutines-core/js/src/Runnable.kt
new file mode 100644
index 00000000..b240a943
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/Runnable.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * A runnable task for [CoroutineDispatcher.dispatch].
+ */
+public actual interface Runnable {
+ /**
+ * @suppress
+ */
+ public actual fun run()
+}
+
+/**
+ * Creates [Runnable] task instance.
+ */
+@Suppress("FunctionName")
+public actual inline fun Runnable(crossinline block: () -> Unit): Runnable =
+ object : Runnable {
+ override fun run() {
+ block()
+ }
+ }
diff --git a/kotlinx-coroutines-core/js/src/SchedulerTask.kt b/kotlinx-coroutines-core/js/src/SchedulerTask.kt
new file mode 100644
index 00000000..dce72f5b
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/SchedulerTask.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+internal actual abstract class SchedulerTask : Runnable
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias SchedulerTaskContext = Unit
+
+internal actual val SchedulerTask.taskContext: SchedulerTaskContext get() = Unit
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun SchedulerTaskContext.afterTask() {}
+
diff --git a/kotlinx-coroutines-core/js/src/Window.kt b/kotlinx-coroutines-core/js/src/Window.kt
new file mode 100644
index 00000000..3c1cdb42
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/Window.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-2018 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.w3c.dom.Window
+
+/**
+ * Converts an instance of [Window] to an implementation of [CoroutineDispatcher].
+ */
+public fun Window.asCoroutineDispatcher(): CoroutineDispatcher =
+ @Suppress("UnsafeCastFromDynamic")
+ asDynamic().coroutineDispatcher ?: WindowDispatcher(this).also {
+ asDynamic().coroutineDispatcher = it
+ }
+
+/**
+ * Suspends coroutine until next JS animation frame and returns frame time on resumption.
+ * The time is consistent with [window.performance.now()][org.w3c.performance.Performance.now].
+ * This function is cancellable. If the [Job] of the current coroutine is completed while this suspending
+ * function is waiting, this function immediately resumes with [CancellationException].
+ */
+public suspend fun Window.awaitAnimationFrame(): Double = suspendCancellableCoroutine { cont ->
+ asWindowAnimationQueue().enqueue(cont)
+}
+
+private fun Window.asWindowAnimationQueue(): WindowAnimationQueue =
+ @Suppress("UnsafeCastFromDynamic")
+ asDynamic().coroutineAnimationQueue ?: WindowAnimationQueue(this).also {
+ asDynamic().coroutineAnimationQueue = it
+ }
+
+private class WindowAnimationQueue(private val window: Window) {
+ private val dispatcher = window.asCoroutineDispatcher()
+ private var scheduled = false
+ private var current = ArrayQueue<CancellableContinuation<Double>>()
+ private var next = ArrayQueue<CancellableContinuation<Double>>()
+ private var timestamp = 0.0
+
+ fun enqueue(cont: CancellableContinuation<Double>) {
+ next.addLast(cont)
+ if (!scheduled) {
+ scheduled = true
+ window.requestAnimationFrame { ts ->
+ timestamp = ts
+ val prev = current
+ current = next
+ next = prev
+ scheduled = false
+ process()
+ }
+ }
+ }
+
+ fun process() {
+ while(true) {
+ val element = current.removeFirstOrNull() ?: return
+ with(element) { dispatcher.resumeUndispatched(timestamp) }
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt
new file mode 100644
index 00000000..8422f2bf
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+
+internal actual class AbortFlowException : CancellationException("Flow was aborted, no more elements needed")
+internal actual class ChildCancelledException : CancellationException("Child of the scoped flow was cancelled")
diff --git a/kotlinx-coroutines-core/js/src/internal/Concurrent.kt b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt
new file mode 100644
index 00000000..ec8f6b11
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+internal actual typealias ReentrantLock = NoOpLock
+
+internal actual inline fun <T> ReentrantLock.withLock(action: () -> T) = action()
+
+internal class NoOpLock {
+ fun tryLock() = true
+ fun unlock(): Unit {}
+}
+
+internal actual fun <E> subscriberList(): SubscribersList<E> = CopyOnWriteList()
+
+internal actual fun <E> identitySet(expectedSize: Int): MutableSet<E> = HashSet(expectedSize)
diff --git a/kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt
new file mode 100644
index 00000000..8a7a55d9
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+/**
+ * Analogue of java.util.concurrent.CopyOnWriteArrayList for JS.
+ * Even though JS has no real concurrency, [CopyOnWriteList] is essential to manage any kinds
+ * of callbacks or continuations.
+ *
+ * Implementation note: most of the methods fallbacks to [AbstractMutableList] (thus inefficient for CoW pattern)
+ * and some methods are unsupported, because currently they are not required for this class consumers.
+ */
+internal class CopyOnWriteList<E>(private var array: Array<E> = emptyArray()) : AbstractMutableList<E>() {
+
+ override val size: Int get() = array.size
+
+ override fun add(element: E): Boolean {
+ val copy = array.asDynamic().slice()
+ copy.push(element)
+ array = copy as Array<E>
+ return true
+ }
+
+ override fun add(index: Int, element: E) {
+ val copy = array.asDynamic().slice()
+ copy.splice(insertionRangeCheck(index), 0, element)
+ array = copy as Array<E>
+ }
+
+ override fun remove(element: E): Boolean {
+ for (index in array.indices) {
+ if (array[index] == element) {
+ val copy = array.asDynamic().slice()
+ copy.splice(index, 1)
+ array = copy as Array<E>
+ return true
+ }
+ }
+
+ return false
+ }
+
+ override fun removeAt(index: Int): E {
+ rangeCheck(index)
+ val copy = array.asDynamic().slice()
+ val result = if (index == lastIndex) {
+ copy.pop()
+ } else {
+ copy.splice(index, 1)[0]
+ }
+
+ array = copy as Array<E>
+ return result as E
+ }
+
+ override fun iterator(): MutableIterator<E> = IteratorImpl(array)
+
+ override fun listIterator(): MutableListIterator<E> = throw UnsupportedOperationException("Operation is not supported")
+
+ override fun listIterator(index: Int): MutableListIterator<E> = throw UnsupportedOperationException("Operation is not supported")
+
+ override fun isEmpty(): Boolean = size == 0
+
+ override fun set(index: Int, element: E): E = throw UnsupportedOperationException("Operation is not supported")
+
+ override fun get(index: Int): E = array[rangeCheck(index)]
+
+ private class IteratorImpl<E>(private var array: Array<E>) : MutableIterator<E> {
+
+ private var current = 0
+
+ override fun hasNext(): Boolean = current != array.size
+
+ override fun next(): E {
+ if (!hasNext()) {
+ throw NoSuchElementException()
+ }
+
+ return array[current++]
+ }
+
+ override fun remove() = throw UnsupportedOperationException("Operation is not supported")
+ }
+
+ private fun insertionRangeCheck(index: Int) {
+ if (index < 0 || index > size) {
+ throw IndexOutOfBoundsException("index: $index, size: $size")
+ }
+ }
+
+ private fun rangeCheck(index: Int) = index.apply {
+ if (index < 0 || index >= size) {
+ throw IndexOutOfBoundsException("index: $index, size: $size")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
new file mode 100644
index 00000000..60509010
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused")
+
+package kotlinx.coroutines.internal
+
+private typealias Node = LinkedListNode
+
+/** @suppress **This is unstable API and it is subject to change.** */
+@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // :TODO: Remove when fixed: https://youtrack.jetbrains.com/issue/KT-23703
+public actual typealias LockFreeLinkedListNode = LinkedListNode
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual typealias LockFreeLinkedListHead = LinkedListHead
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public open class LinkedListNode {
+ @PublishedApi internal var _next = this
+ @PublishedApi internal var _prev = this
+ @PublishedApi internal var _removed: Boolean = false
+
+ public inline val nextNode get() = _next
+ public inline val prevNode get() = _prev
+ public inline val isRemoved get() = _removed
+
+ public fun addLast(node: Node) {
+ val prev = this._prev
+ node._next = this
+ node._prev = prev
+ prev._next = node
+ this._prev = node
+ }
+
+ public open fun remove(): Boolean {
+ if (_removed) return false
+ val prev = this._prev
+ val next = this._next
+ prev._next = next
+ next._prev = prev
+ _removed = true
+ return true
+ }
+
+ public fun addOneIfEmpty(node: Node): Boolean {
+ if (_next !== this) return false
+ addLast(node)
+ return true
+ }
+
+ public inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean {
+ if (!condition()) return false
+ addLast(node)
+ return true
+ }
+
+ public inline fun addLastIfPrev(node: Node, predicate: (Node) -> Boolean): Boolean {
+ if (!predicate(_prev)) return false
+ addLast(node)
+ return true
+ }
+
+ public inline fun addLastIfPrevAndIf(
+ node: Node,
+ predicate: (Node) -> Boolean, // prev node predicate
+ crossinline condition: () -> Boolean // atomically checked condition
+ ): Boolean {
+ if (!predicate(_prev)) return false
+ if (!condition()) return false
+ addLast(node)
+ return true
+ }
+
+ public fun helpRemove() {} // No concurrency on JS -> no removal
+
+ public fun removeFirstOrNull(): Node? {
+ val next = _next
+ if (next === this) return null
+ check(next.remove()) { "Should remove" }
+ return next
+ }
+
+ public inline fun <reified T> removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? {
+ val next = _next
+ if (next === this) return null
+ if (next !is T) return null
+ if (predicate(next)) return next
+ check(next.remove()) { "Should remove" }
+ return next
+ }
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual open class AddLastDesc<T : Node> actual constructor(
+ actual val queue: Node,
+ actual val node: T
+) : AbstractAtomicDesc() {
+ protected override val affectedNode: Node get() = queue._prev
+ protected actual override fun onPrepare(affected: Node, next: Node): Any? = null
+ protected override fun onComplete() = queue.addLast(node)
+ protected actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual open class RemoveFirstDesc<T> actual constructor(
+ actual val queue: LockFreeLinkedListNode
+) : AbstractAtomicDesc() {
+
+ @Suppress("UNCHECKED_CAST")
+ public actual val result: T get() = affectedNode as T
+ protected override val affectedNode: Node = queue.nextNode
+ protected actual open fun validatePrepared(node: T): Boolean = true
+ protected actual final override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? {
+ @Suppress("UNCHECKED_CAST")
+ validatePrepared(affectedNode as T)
+ return null
+ }
+ protected override fun onComplete() { queue.removeFirstOrNull() }
+ protected actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual abstract class AbstractAtomicDesc : AtomicDesc() {
+ protected abstract val affectedNode: Node
+ protected actual abstract fun onPrepare(affected: Node, next: Node): Any?
+ protected abstract fun onComplete()
+
+ actual final override fun prepare(op: AtomicOp<*>): Any? {
+ val affected = affectedNode
+ val next = affected._next
+ val failure = failure(affected)
+ if (failure != null) return failure
+ return onPrepare(affected, next)
+ }
+
+ actual final override fun complete(op: AtomicOp<*>, failure: Any?) = onComplete()
+ protected actual open fun failure(affected: LockFreeLinkedListNode): Any? = null // Never fails by default
+ protected actual open fun retry(affected: LockFreeLinkedListNode, next: Any): Boolean = false // Always succeeds
+ protected actual abstract fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode)
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public open class LinkedListHead : LinkedListNode() {
+ public val isEmpty get() = _next === this
+
+ /**
+ * Iterates over all elements in this list of a specified type.
+ */
+ public inline fun <reified T : Node> forEach(block: (T) -> Unit) {
+ var cur: Node = _next
+ while (cur != this) {
+ if (cur is T) block(cur)
+ cur = cur._next
+ }
+ }
+
+ // just a defensive programming -- makes sure that list head sentinel is never removed
+ public final override fun remove(): Boolean = throw UnsupportedOperationException()
+}
diff --git a/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt
new file mode 100644
index 00000000..81b6476b
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> = completion
diff --git a/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt
new file mode 100644
index 00000000..57c6247b
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+internal actual fun <E: Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E = exception
+internal actual fun <E: Throwable> recoverStackTrace(exception: E): E = exception
+internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing = throw exception
+
+internal actual fun <E : Throwable> unwrap(exception: E): E = exception
+
+@Suppress("UNUSED")
+internal actual interface CoroutineStackFrame {
+ public actual val callerFrame: CoroutineStackFrame?
+ public actual fun getStackTraceElement(): StackTraceElement?
+}
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias StackTraceElement = Any
diff --git a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
new file mode 100644
index 00000000..fbed5462
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual typealias SynchronizedObject = Any
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
+ block() \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/internal/SystemProps.kt b/kotlinx-coroutines-core/js/src/internal/SystemProps.kt
new file mode 100644
index 00000000..6779d4e5
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/SystemProps.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+internal actual fun systemProp(propertyName: String): String? = null \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt
new file mode 100644
index 00000000..d9daf256
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+internal actual fun threadContextElements(context: CoroutineContext): Any = 0
diff --git a/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt
new file mode 100644
index 00000000..2ee0a4fd
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+internal actual class CommonThreadLocal<T> actual constructor() {
+ private var value: T? = null
+ @Suppress("UNCHECKED_CAST")
+ actual fun get(): T = value as T
+ actual fun set(value: T) { this.value = value }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/test/MessageQueueTest.kt b/kotlinx-coroutines-core/js/test/MessageQueueTest.kt
new file mode 100644
index 00000000..de514c76
--- /dev/null
+++ b/kotlinx-coroutines-core/js/test/MessageQueueTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class MessageQueueTest {
+ private var scheduled = false
+ private val processed = mutableListOf<Int>()
+
+ private val queue = object : MessageQueue() {
+ override fun schedule() {
+ assertFalse(scheduled)
+ scheduled = true
+ }
+
+ override fun reschedule() {
+ schedule()
+ }
+ }
+
+ inner class Box(val i: Int): Runnable {
+ override fun run() {
+ processed += i
+ }
+ }
+
+ inner class ReBox(val i: Int): Runnable {
+ override fun run() {
+ processed += i
+ queue.enqueue(Box(10 + i))
+ }
+ }
+
+ @Test
+ fun testBasic() {
+ assertTrue(queue.isEmpty)
+ queue.enqueue(Box(1))
+ assertFalse(queue.isEmpty)
+ assertTrue(scheduled)
+ queue.enqueue(Box(2))
+ assertFalse(queue.isEmpty)
+ scheduled = false
+ queue.process()
+ assertEquals(listOf(1, 2), processed)
+ assertFalse(scheduled)
+ assertTrue(queue.isEmpty)
+ }
+
+ @Test fun testRescheduleFromProcess() {
+ assertTrue(queue.isEmpty)
+ queue.enqueue(ReBox(1))
+ assertFalse(queue.isEmpty)
+ assertTrue(scheduled)
+ queue.enqueue(ReBox(2))
+ assertFalse(queue.isEmpty)
+ scheduled = false
+ queue.process()
+ assertEquals(listOf(1, 2, 11, 12), processed)
+ assertFalse(scheduled)
+ assertTrue(queue.isEmpty)
+ }
+
+ @Test
+ fun testResizeAndWrap() {
+ repeat(10) { phase ->
+ val n = 10 * (phase + 1)
+ assertTrue(queue.isEmpty)
+ repeat(n) {
+ queue.enqueue(Box(it))
+ assertFalse(queue.isEmpty)
+ assertTrue(scheduled)
+ }
+ var countYields = 0
+ while (scheduled) {
+ scheduled = false
+ queue.process()
+ countYields++
+ }
+ assertEquals(List(n) { it }, processed)
+ assertEquals((n + queue.yieldEvery - 1) / queue.yieldEvery, countYields)
+ processed.clear()
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/test/PromiseTest.kt b/kotlinx-coroutines-core/js/test/PromiseTest.kt
new file mode 100644
index 00000000..d0f6b2b7
--- /dev/null
+++ b/kotlinx-coroutines-core/js/test/PromiseTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.js.*
+import kotlin.test.*
+
+class PromiseTest : TestBase() {
+ @Test
+ fun testPromiseResolvedAsDeferred() = GlobalScope.promise {
+ val promise = Promise<String> { resolve, _ ->
+ resolve("OK")
+ }
+ val deferred = promise.asDeferred()
+ assertEquals("OK", deferred.await())
+ }
+
+ @Test
+ fun testPromiseRejectedAsDeferred() = GlobalScope.promise {
+ lateinit var promiseReject: (Throwable) -> Unit
+ val promise = Promise<String> { _, reject ->
+ promiseReject = reject
+ }
+ val deferred = promise.asDeferred()
+ // reject after converting to deferred to avoid "Unhandled promise rejection" warnings
+ promiseReject(TestException("Rejected"))
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: Throwable) {
+ assertTrue(e is TestException)
+ assertEquals("Rejected", e.message)
+ }
+ }
+
+ @Test
+ fun testCompletedDeferredAsPromise() = GlobalScope.promise {
+ val deferred = async(start = CoroutineStart.UNDISPATCHED) {
+ // completed right away
+ "OK"
+ }
+ val promise = deferred.asPromise()
+ assertEquals("OK", promise.await())
+ }
+
+ @Test
+ fun testWaitForDeferredAsPromise() = GlobalScope.promise {
+ val deferred = async {
+ // will complete later
+ "OK"
+ }
+ val promise = deferred.asPromise()
+ assertEquals("OK", promise.await()) // await yields main thread to deferred coroutine
+ }
+
+ @Test
+ fun testCancellableAwaitPromise() = GlobalScope.promise {
+ lateinit var r: (String) -> Unit
+ val toAwait = Promise<String> { resolve, _ -> r = resolve }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ toAwait.await() // suspends
+ }
+ job.cancel() // cancel the job
+ r("fail") // too late, the waiting job was already cancelled
+ }
+
+ @Test
+ fun testAsPromiseAsDeferred() = GlobalScope.promise {
+ val deferred = async { "OK" }
+ val promise = deferred.asPromise()
+ val d2 = promise.asDeferred()
+ assertSame(d2, deferred)
+ assertEquals("OK", d2.await())
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/test/SetTimeoutDispatcherTest.kt b/kotlinx-coroutines-core/js/test/SetTimeoutDispatcherTest.kt
new file mode 100644
index 00000000..78700776
--- /dev/null
+++ b/kotlinx-coroutines-core/js/test/SetTimeoutDispatcherTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class SetTimeoutDispatcherTest : TestBase() {
+ @Test
+ fun testDispatch() = runTest {
+ launch(SetTimeoutDispatcher) {
+ expect(1)
+ launch {
+ expect(3)
+ }
+ expect(2)
+ yield()
+ expect(4)
+ }.join()
+ finish(5)
+ }
+
+ @Test
+ fun testDelay() = runTest {
+ withContext(SetTimeoutDispatcher) {
+ val job = launch(SetTimeoutDispatcher) {
+ expect(2)
+ delay(100)
+ expect(4)
+ }
+ expect(1)
+ yield() // Yield uses microtask, so should be in the same context
+ expect(3)
+ job.join()
+ finish(5)
+ }
+ }
+
+ @Test
+ fun testWithTimeout() = runTest {
+ withContext(SetTimeoutDispatcher) {
+ val result = withTimeoutOrNull(10) {
+ expect(1)
+ delay(100)
+ expectUnreached()
+ 42
+ }
+ assertNull(result)
+ finish(2)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt
new file mode 100644
index 00000000..3bf49ef8
--- /dev/null
+++ b/kotlinx-coroutines-core/js/test/TestBase.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.js.*
+
+public actual open class TestBase actual constructor() {
+ public actual val isStressTest: Boolean = false
+ public actual val stressTestMultiplier: Int = 1
+
+ private var actionIndex = 0
+ private var finished = false
+ private var error: Throwable? = null
+
+ /**
+ * 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.
+ */
+ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
+ public actual fun error(message: Any, cause: Throwable? = null): Nothing {
+ if (cause != null) console.log(cause)
+ val exception = IllegalStateException(
+ if (cause == null) message.toString() else "$message; caused by $cause")
+ if (error == null) error = exception
+ throw exception
+ }
+
+ private fun printError(message: String, cause: Throwable) {
+ if (error == null) error = cause
+ println("$message: $cause")
+ console.log(cause)
+ }
+
+ /**
+ * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
+ */
+ public actual fun expect(index: Int) {
+ val wasIndex = ++actionIndex
+ check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
+ }
+
+ /**
+ * Asserts that this line is never executed.
+ */
+ public actual fun expectUnreached() {
+ error("Should not be reached")
+ }
+
+ /**
+ * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
+ */
+ public actual fun finish(index: Int) {
+ expect(index)
+ check(!finished) { "Should call 'finish(...)' at most once" }
+ finished = true
+ }
+
+ /**
+ * Asserts that [finish] was invoked
+ */
+ public actual fun ensureFinished() {
+ require(finished) { "finish(...) should be caller prior to this check" }
+ }
+
+ public actual fun reset() {
+ check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
+ actionIndex = 0
+ 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 {
+ var exCount = 0
+ var ex: Throwable? = null
+ return GlobalScope.promise(block = block, context = CoroutineExceptionHandler { context, e ->
+ if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
+ exCount++
+ when {
+ exCount > unhandled.size ->
+ printError("Too many unhandled exceptions $exCount, expected ${unhandled.size}, got: $e", e)
+ !unhandled[exCount - 1](e) ->
+ printError("Unhandled exception was unexpected: $e", e)
+ }
+ }).catch { e ->
+ ex = e
+ if (expected != null) {
+ if (!expected(e))
+ error("Unexpected exception", e)
+ } else
+ throw e
+ }.finally {
+ if (ex == null && expected != null) error("Exception was expected but none produced")
+ if (exCount < unhandled.size)
+ error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
+ error?.let { throw it }
+ check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
+ }
+ }
+}
+
+private fun <T> Promise<T>.finally(block: () -> Unit): Promise<T> =
+ then(onFulfilled = { value -> block(); value }, onRejected = { ex -> block(); throw ex })
diff --git a/kotlinx-coroutines-core/js/test/internal/LinkedListTest.kt b/kotlinx-coroutines-core/js/test/internal/LinkedListTest.kt
new file mode 100644
index 00000000..6c1fddfc
--- /dev/null
+++ b/kotlinx-coroutines-core/js/test/internal/LinkedListTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class LinkedListTest {
+ data class IntNode(val i: Int) : LinkedListNode()
+
+ @Test
+ fun testSimpleAddLastRemove() {
+ val list = LinkedListHead()
+ assertContents(list)
+ val n1 = IntNode(1).apply { list.addLast(this) }
+ assertContents(list, 1)
+ val n2 = IntNode(2).apply { list.addLast(this) }
+ assertContents(list, 1, 2)
+ val n3 = IntNode(3).apply { list.addLast(this) }
+ assertContents(list, 1, 2, 3)
+ val n4 = IntNode(4).apply { list.addLast(this) }
+ assertContents(list, 1, 2, 3, 4)
+ assertTrue(n1.remove())
+ assertContents(list, 2, 3, 4)
+ assertTrue(n3.remove())
+ assertContents(list, 2, 4)
+ assertTrue(n4.remove())
+ assertContents(list, 2)
+ assertTrue(n2.remove())
+ assertFalse(n2.remove())
+ assertContents(list)
+ }
+
+ private fun assertContents(list: LinkedListHead, vararg expected: Int) {
+ val n = expected.size
+ val actual = IntArray(n)
+ var index = 0
+ list.forEach<IntNode> { actual[index++] = it.i }
+ assertEquals(n, index)
+ for (i in 0 until n) assertEquals(expected[i], actual[i], "item i")
+ assertEquals(expected.isEmpty(), list.isEmpty)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
new file mode 100644
index 00000000..31c464f6
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
@@ -0,0 +1,10 @@
+# ServiceLoader support
+-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
+-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
+-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
+-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
+
+# Most of volatile fields are updated with AFU and should not be mangled
+-keepclassmembernames class kotlinx.** {
+ volatile <fields>;
+}
diff --git a/kotlinx-coroutines-core/jvm/src/Builders.kt b/kotlinx-coroutines-core/jvm/src/Builders.kt
new file mode 100644
index 00000000..ac3cade0
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/Builders.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("BuildersKt")
+
+package kotlinx.coroutines
+
+import java.util.concurrent.locks.*
+import kotlin.coroutines.*
+
+/**
+ * Runs a new coroutine and **blocks** the current thread _interruptibly_ until its completion.
+ * This function should not be used from a coroutine. It is designed to bridge regular blocking code
+ * to libraries that are written in suspending style, to be used in `main` functions and in tests.
+ *
+ * The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations
+ * in this blocked thread until the completion of this coroutine.
+ * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`.
+ *
+ * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of
+ * the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another `runBlocking`,
+ * then this invocation uses the outer event loop.
+ *
+ * If this blocked thread is interrupted (see [Thread.interrupt]), then the coroutine job is cancelled and
+ * this `runBlocking` invocation throws [InterruptedException].
+ *
+ * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available
+ * for a newly created coroutine.
+ *
+ * @param context the context of the coroutine. The default value is an event loop on the current thread.
+ * @param block the coroutine code.
+ */
+@Throws(InterruptedException::class)
+public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
+ val currentThread = Thread.currentThread()
+ val contextInterceptor = context[ContinuationInterceptor]
+ val eventLoop: EventLoop?
+ val newContext: CoroutineContext
+ if (contextInterceptor == null) {
+ // create or use private event loop if no dispatcher is specified
+ eventLoop = ThreadLocalEventLoop.eventLoop
+ newContext = GlobalScope.newCoroutineContext(context + eventLoop)
+ } else {
+ // See if context's interceptor is an event loop that we shall use (to support TestContext)
+ // or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
+ eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
+ ?: ThreadLocalEventLoop.currentOrNull()
+ newContext = GlobalScope.newCoroutineContext(context)
+ }
+ val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+ return coroutine.joinBlocking()
+}
+
+private class BlockingCoroutine<T>(
+ parentContext: CoroutineContext,
+ private val blockedThread: Thread,
+ private val eventLoop: EventLoop?
+) : AbstractCoroutine<T>(parentContext, true) {
+ override val isScopedCoroutine: Boolean get() = true
+
+ override fun afterCompletionInternal(state: Any?, mode: Int) {
+ // wake up blocked thread
+ if (Thread.currentThread() != blockedThread)
+ LockSupport.unpark(blockedThread)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun joinBlocking(): T {
+ registerTimeLoopThread()
+ try {
+ eventLoop?.incrementUseCount()
+ try {
+ while (true) {
+ @Suppress("DEPRECATION")
+ if (Thread.interrupted()) throw InterruptedException().also { cancelCoroutine(it) }
+ val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
+ // note: process next even may loose unpark flag, so check if completed before parking
+ if (isCompleted) break
+ parkNanos(this, parkNanos)
+ }
+ } finally { // paranoia
+ eventLoop?.decrementUseCount()
+ }
+ } finally { // paranoia
+ unregisterTimeLoopThread()
+ }
+ // now return result
+ val state = this.state.unboxState()
+ (state as? CompletedExceptionally)?.let { throw it.cause }
+ return state as T
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/CommonPool.kt b/kotlinx-coroutines-core/jvm/src/CommonPool.kt
new file mode 100644
index 00000000..1b5aae98
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/CommonPool.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import kotlin.coroutines.*
+
+/**
+ * Represents common pool of shared threads as coroutine dispatcher for compute-intensive tasks.
+ *
+ * If there isn't a SecurityManager present it uses [java.util.concurrent.ForkJoinPool] when available, which implements
+ * efficient work-stealing algorithm for its queues, so every coroutine resumption is dispatched as a separate task even
+ * when it already executes inside the pool. When available, it wraps `ForkJoinPool.commonPool` and provides a similar
+ * shared pool where not.
+ *
+ * If there is a SecurityManager present (as would be if running inside a Java Web Start context) then a plain thread
+ * pool is created. This is to work around the fact that ForkJoinPool creates threads that cannot perform
+ * privileged actions.
+ */
+internal object CommonPool : ExecutorCoroutineDispatcher() {
+
+ /**
+ * Name of the property that controls default parallelism level of [CommonPool].
+ * If the property is not specified, `Runtime.getRuntime().availableProcessors() - 1` will be used instead (or `1` for single-core JVM).
+ * 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"
+
+ override val executor: Executor
+ get() = pool ?: getOrCreatePoolSync()
+
+ // Equals to -1 if not explicitly specified
+ private val requestedParallelism = run<Int> {
+ val property = Try { System.getProperty(DEFAULT_PARALLELISM_PROPERTY_NAME) } ?: return@run -1
+ val parallelism = property.toIntOrNull()
+ if (parallelism == null || parallelism < 1) {
+ error("Expected positive number in $DEFAULT_PARALLELISM_PROPERTY_NAME, but has $property")
+ }
+ parallelism
+ }
+
+ private val parallelism: Int
+ get() = requestedParallelism.takeIf { it > 0 }
+ ?: (Runtime.getRuntime().availableProcessors() - 1).coerceAtLeast(1)
+
+ // For debug and tests
+ private var usePrivatePool = false
+
+ @Volatile
+ private var pool: Executor? = null
+
+ private inline fun <T> Try(block: () -> T) = try { block() } catch (e: Throwable) { null }
+
+ private fun createPool(): ExecutorService {
+ if (System.getSecurityManager() != null) return createPlainPool()
+ // Reflection on ForkJoinPool class so that it works on JDK 6 (which is absent there)
+ val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
+ ?: 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 }
+ ?.takeIf { isGoodCommonPool(fjpClass, it) }
+ ?.let { return it }
+ }
+ // Try to create private ForkJoinPool instance
+ Try { fjpClass.getConstructor(Int::class.java).newInstance(parallelism) as? ExecutorService }
+ ?. let { return it }
+ // Fallback to plain thread pool
+ return createPlainPool()
+ }
+
+ /**
+ * Checks that this ForkJoinPool's parallelism is at least one to avoid pathological bugs.
+ */
+ internal fun isGoodCommonPool(fjpClass: Class<*>, executor: ExecutorService): Boolean {
+ // We cannot use getParallelism, since it lies to us (always returns at least 1)
+ // So we submit a task and check that getPoolSize is at least one after that
+ // A broken FJP (that is configured for 0 parallelism) would not execute the task and
+ // would report its pool size as zero.
+ executor.submit {}
+ val actual = Try { fjpClass.getMethod("getPoolSize").invoke(executor) as? Int }
+ ?: return false
+ return actual >= 1
+ }
+
+ private fun createPlainPool(): ExecutorService {
+ val threadId = AtomicInteger()
+ return Executors.newFixedThreadPool(parallelism) {
+ Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }
+ }
+ }
+
+ @Synchronized
+ private fun getOrCreatePoolSync(): Executor =
+ pool ?: createPool().also { pool = it }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ try {
+ (pool ?: getOrCreatePoolSync()).execute(wrapTask(block))
+ } catch (e: RejectedExecutionException) {
+ unTrackTask()
+ DefaultExecutor.enqueue(block)
+ }
+ }
+
+ // used for tests
+ @Synchronized
+ internal fun usePrivatePool() {
+ shutdown(0)
+ usePrivatePool = true
+ pool = null
+ }
+
+ // used for tests
+ @Synchronized
+ internal fun shutdown(timeout: Long) {
+ (pool as? ExecutorService)?.apply {
+ shutdown()
+ if (timeout > 0)
+ awaitTermination(timeout, TimeUnit.MILLISECONDS)
+ shutdownNow().forEach { DefaultExecutor.enqueue(it) }
+ }
+ pool = Executor { throw RejectedExecutionException("CommonPool was shutdown") }
+ }
+
+ // used for tests
+ @Synchronized
+ internal fun restore() {
+ shutdown(0)
+ usePrivatePool = false
+ pool = null
+ }
+
+ override fun toString(): String = "CommonPool"
+
+ override fun close(): Unit = error("Close cannot be invoked on CommonPool")
+}
diff --git a/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt b/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt
new file mode 100644
index 00000000..d425d6d8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+
+internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler {
+ actual abstract override fun invoke(cause: Throwable?)
+}
+
+internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler get() = this
+
+internal actual abstract class CancelHandlerBase actual constructor() : CompletionHandler {
+ actual abstract override fun invoke(cause: Throwable?)
+}
+
+internal actual inline val CancelHandlerBase.asHandler: CompletionHandler get() = this
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun CompletionHandler.invokeIt(cause: Throwable?) = invoke(cause) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
new file mode 100644
index 00000000..d0375a61
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2018 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.*
+
+internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"
+
+internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
+ when (value) {
+ null, "", "on" -> true
+ "off" -> false
+ else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'")
+ }
+}
+
+internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
+ if (useCoroutinesScheduler) DefaultScheduler else CommonPool
+
+/**
+ * Creates context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher nor
+ * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on).
+ *
+ * See [DEBUG_PROPERTY_NAME] for description of debugging facilities on JVM.
+ */
+@ExperimentalCoroutinesApi
+public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
+ val combined = coroutineContext + context
+ val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
+ return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
+ debug + Dispatchers.Default else debug
+}
+
+/**
+ * Executes a block using a given coroutine context.
+ */
+internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T {
+ val oldValue = updateThreadContext(context, countOrElement)
+ try {
+ return block()
+ } finally {
+ restoreThreadContext(context, oldValue)
+ }
+}
+
+internal actual val CoroutineContext.coroutineName: String? get() {
+ if (!DEBUG) return null
+ val coroutineId = this[CoroutineId] ?: return null
+ val coroutineName = this[CoroutineName]?.name ?: "coroutine"
+ return "$coroutineName#${coroutineId.id}"
+}
+
+private const val DEBUG_THREAD_NAME_SEPARATOR = " @"
+
+internal data class CoroutineId(
+ val id: Long
+) : ThreadContextElement<String>, AbstractCoroutineContextElement(CoroutineId) {
+ companion object Key : CoroutineContext.Key<CoroutineId>
+ override fun toString(): String = "CoroutineId($id)"
+
+ override fun updateThreadContext(context: CoroutineContext): String {
+ val coroutineName = context[CoroutineName]?.name ?: "coroutine"
+ val currentThread = Thread.currentThread()
+ val oldName = currentThread.name
+ var lastIndex = oldName.lastIndexOf(DEBUG_THREAD_NAME_SEPARATOR)
+ if (lastIndex < 0) lastIndex = oldName.length
+ currentThread.name = buildString(lastIndex + coroutineName.length + 10) {
+ append(oldName.substring(0, lastIndex))
+ append(DEBUG_THREAD_NAME_SEPARATOR)
+ append(coroutineName)
+ append('#')
+ append(id)
+ }
+ return oldName
+ }
+
+ override fun restoreThreadContext(context: CoroutineContext, oldState: String) {
+ Thread.currentThread().name = oldState
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
new file mode 100644
index 00000000..90c6a849
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import java.util.*
+import kotlin.coroutines.*
+
+/**
+ * A list of globally installed [CoroutineExceptionHandler] instances.
+ *
+ * Note that Android may have dummy [Thread.contextClassLoader] which is used by one-argument [ServiceLoader.load] function,
+ * see (https://stackoverflow.com/questions/13407006/android-class-loader-may-fail-for-processes-that-host-multiple-applications).
+ * So here we explicitly use two-argument `load` with a class-loader of [CoroutineExceptionHandler] class.
+ *
+ * We are explicitly using the `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`
+ * form of the ServiceLoader call to enable R8 optimization when compiled on Android.
+ */
+private val handlers: List<CoroutineExceptionHandler> = ServiceLoader.load(
+ CoroutineExceptionHandler::class.java,
+ CoroutineExceptionHandler::class.java.classLoader
+).iterator().asSequence().toList()
+
+
+internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
+ // use additional extension handlers
+ for (handler in handlers) {
+ try {
+ handler.handleException(context, exception)
+ } catch (t: Throwable) {
+ // Use thread's handler if custom handler failed to handle exception
+ val currentThread = Thread.currentThread()
+ currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
+ }
+ }
+
+ // use thread's handler
+ val currentThread = Thread.currentThread()
+ currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
+}
diff --git a/kotlinx-coroutines-core/jvm/src/Debug.kt b/kotlinx-coroutines-core/jvm/src/Debug.kt
new file mode 100644
index 00000000..98a1c1ea
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/Debug.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// Need InlineOnly for efficient bytecode on Android
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+import java.util.concurrent.atomic.*
+import kotlin.internal.InlineOnly
+
+/**
+ * Name of the property that controls coroutine debugging.
+ *
+ * ### Debugging facilities
+ *
+ * In debug mode every coroutine is assigned a unique consecutive identifier.
+ * Every thread that executes a coroutine has its name modified to include the name and identifier of
+ * the currently running coroutine.
+ *
+ * Enable debugging facilities with "`kotlinx.coroutines.debug`" ([DEBUG_PROPERTY_NAME]) system property,
+ * use the following values:
+ *
+ * * "`auto`" (default mode, [DEBUG_PROPERTY_VALUE_AUTO]) -- enabled when assertions are enabled with "`-ea`" JVM option.
+ * * "`on`" ([DEBUG_PROPERTY_VALUE_ON]) or empty string -- enabled.
+ * * "`off`" ([DEBUG_PROPERTY_VALUE_OFF]) -- disabled.
+ *
+ * Coroutine name can be explicitly assigned using [CoroutineName] context element.
+ * The string "coroutine" is used as a default name.
+ *
+ * Debugging facilities are implemented by [newCoroutineContext][CoroutineScope.newCoroutineContext] function that
+ * is used in all coroutine builders to create context of a new coroutine.
+ */
+public const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug"
+
+/**
+ * Name of the boolean property that controls stacktrace recovery (enabled by default) on JVM.
+ * Stacktrace recovery is enabled if both debug and stacktrace recovery modes are enabled.
+ *
+ * Stacktrace recovery mode wraps every exception into the exception of the same type with original exception
+ * as cause, but with stacktrace of the current coroutine.
+ * Exception is instantiated using reflection by using no-arg, cause or cause and message constructor.
+ *
+ * This mechanism is currently supported for channels, [async], [launch], [coroutineScope], [supervisorScope]
+ * and [withContext] builders.
+ */
+internal const val STACKTRACE_RECOVERY_PROPERTY_NAME = "kotlinx.coroutines.stacktrace.recovery"
+
+/**
+ * Throwable which can be cloned during stacktrace recovery in a class-specific way.
+ * For additional information about stacktrace recovery see [STACKTRACE_RECOVERY_PROPERTY_NAME]
+ *
+ * Example of usage:
+ * ```
+ * class BadResponseCodeException(val responseCode: Int) : Exception(), CopyableThrowable<BadResponseCodeException> {
+ *
+ * override fun createCopy(): BadResponseCodeException {
+ * val result = BadResponseCodeException(responseCode)
+ * result.initCause(this)
+ * return result
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public interface CopyableThrowable<T> where T : Throwable, T : CopyableThrowable<T> {
+
+ /**
+ * 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.
+ */
+ public fun createCopy(): T?
+}
+
+/**
+ * Automatic debug configuration value for [DEBUG_PROPERTY_NAME].
+ */
+public const val DEBUG_PROPERTY_VALUE_AUTO = "auto"
+
+/**
+ * Debug turned on value for [DEBUG_PROPERTY_NAME].
+ */
+public const val DEBUG_PROPERTY_VALUE_ON = "on"
+
+/**
+ * Debug turned on value for [DEBUG_PROPERTY_NAME].
+ */
+public const val DEBUG_PROPERTY_VALUE_OFF = "off"
+
+// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
+internal val ASSERTIONS_ENABLED = CoroutineId::class.java.desiredAssertionStatus()
+
+// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
+internal actual val DEBUG = systemProp(DEBUG_PROPERTY_NAME).let { value ->
+ when (value) {
+ DEBUG_PROPERTY_VALUE_AUTO, null -> ASSERTIONS_ENABLED
+ DEBUG_PROPERTY_VALUE_ON, "" -> true
+ DEBUG_PROPERTY_VALUE_OFF -> false
+ else -> error("System property '$DEBUG_PROPERTY_NAME' has unrecognized value '$value'")
+ }
+}
+
+// Note: stack-trace recovery is enabled only in debug mode
+// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
+internal actual val RECOVER_STACK_TRACES =
+ DEBUG && systemProp(STACKTRACE_RECOVERY_PROPERTY_NAME, true)
+
+// It is used only in debug mode
+internal val COROUTINE_ID = AtomicLong(0)
+
+// for tests only
+internal fun resetCoroutineId() {
+ COROUTINE_ID.set(0)
+}
+
+@InlineOnly
+internal actual inline fun assert(value: () -> Boolean) {
+ if (ASSERTIONS_ENABLED && !value()) throw AssertionError()
+}
diff --git a/kotlinx-coroutines-core/jvm/src/DebugStrings.kt b/kotlinx-coroutines-core/jvm/src/DebugStrings.kt
new file mode 100644
index 00000000..78ad4181
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/DebugStrings.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+// internal debugging tools for string representation
+
+internal actual val Any.hexAddress: String
+ get() = Integer.toHexString(System.identityHashCode(this))
+
+internal actual fun Continuation<*>.toDebugString(): String = when (this) {
+ is DispatchedContinuation -> toString()
+ // Workaround for #858
+ else -> runCatching { "$this@$hexAddress" }.getOrElse { "${this::class.java.name}@$hexAddress" }
+}
+
+internal actual val Any.classSimpleName: String get() = this::class.java.simpleName
diff --git a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
new file mode 100644
index 00000000..19adcef2
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import java.util.concurrent.*
+
+internal actual val DefaultDelay: Delay = DefaultExecutor
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+internal actual object DefaultExecutor : EventLoopImplBase(), Runnable {
+ const val THREAD_NAME = "kotlinx.coroutines.DefaultExecutor"
+
+ init {
+ incrementUseCount() // this event loop is never completed
+ }
+
+ private const val DEFAULT_KEEP_ALIVE = 1000L // in milliseconds
+
+ private val KEEP_ALIVE_NANOS = TimeUnit.MILLISECONDS.toNanos(
+ try {
+ java.lang.Long.getLong("kotlinx.coroutines.DefaultExecutor.keepAlive", DEFAULT_KEEP_ALIVE)
+ } catch (e: SecurityException) {
+ DEFAULT_KEEP_ALIVE
+ })
+
+ @Suppress("ObjectPropertyName")
+ @Volatile
+ private var _thread: Thread? = null
+
+ override val thread: Thread
+ get() = _thread ?: createThreadSync()
+
+ private const val FRESH = 0
+ private const val ACTIVE = 1
+ private const val SHUTDOWN_REQ = 2
+ private const val SHUTDOWN_ACK = 3
+
+ @Volatile
+ private var debugStatus: Int = FRESH
+
+ private val isShutdownRequested: Boolean get() {
+ val debugStatus = debugStatus
+ return debugStatus == SHUTDOWN_REQ || debugStatus == SHUTDOWN_ACK
+ }
+
+ /**
+ * All event loops are using DefaultExecutor#invokeOnTimeout to avoid livelock on
+ * ```
+ * runBlocking(eventLoop) { withTimeout { while(isActive) { ... } } }
+ * ```
+ *
+ * Livelock is possible only if `runBlocking` is called on internal default executed (which is used by default [delay]),
+ * but it's not exposed as public API.
+ */
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle =
+ scheduleInvokeOnTimeout(timeMillis, block)
+
+ override fun run() {
+ ThreadLocalEventLoop.setEventLoop(this)
+ registerTimeLoopThread()
+ try {
+ var shutdownNanos = Long.MAX_VALUE
+ if (!notifyStartup()) return
+ while (true) {
+ Thread.interrupted() // just reset interruption flag
+ var parkNanos = processNextEvent()
+ if (parkNanos == Long.MAX_VALUE) {
+ // nothing to do, initialize shutdown timeout
+ if (shutdownNanos == Long.MAX_VALUE) {
+ val now = nanoTime()
+ if (shutdownNanos == Long.MAX_VALUE) shutdownNanos = now + KEEP_ALIVE_NANOS
+ val tillShutdown = shutdownNanos - now
+ if (tillShutdown <= 0) return // shut thread down
+ parkNanos = parkNanos.coerceAtMost(tillShutdown)
+ } else
+ parkNanos = parkNanos.coerceAtMost(KEEP_ALIVE_NANOS) // limit wait time anyway
+ }
+ if (parkNanos > 0) {
+ // check if shutdown was requested and bail out in this case
+ if (isShutdownRequested) return
+ parkNanos(this, parkNanos)
+ }
+ }
+ } finally {
+ _thread = null // this thread is dead
+ acknowledgeShutdownIfNeeded()
+ unregisterTimeLoopThread()
+ // recheck if queues are empty after _thread reference was set to null (!!!)
+ if (!isEmpty) thread // recreate thread if it is needed
+ }
+ }
+
+ @Synchronized
+ private fun createThreadSync(): Thread {
+ return _thread ?: Thread(this, THREAD_NAME).apply {
+ _thread = this
+ isDaemon = true
+ start()
+ }
+ }
+
+ // used for tests
+ @Synchronized
+ internal fun ensureStarted() {
+ assert { _thread == null } // ensure we are at a clean state
+ assert { debugStatus == FRESH || debugStatus == SHUTDOWN_ACK }
+ debugStatus = FRESH
+ createThreadSync() // create fresh thread
+ while (debugStatus == FRESH) (this as Object).wait()
+ }
+
+ @Synchronized
+ private fun notifyStartup(): Boolean {
+ if (isShutdownRequested) return false
+ debugStatus = ACTIVE
+ (this as Object).notifyAll()
+ return true
+ }
+
+ // used for tests
+ @Synchronized
+ fun shutdown(timeout: Long) {
+ val deadline = System.currentTimeMillis() + timeout
+ if (!isShutdownRequested) debugStatus = SHUTDOWN_REQ
+ // loop while there is anything to do immediately or deadline passes
+ while (debugStatus != SHUTDOWN_ACK && _thread != null) {
+ _thread?.let { unpark(it) } // wake up thread if present
+ val remaining = deadline - System.currentTimeMillis()
+ if (remaining <= 0) break
+ (this as Object).wait(timeout)
+ }
+ // restore fresh status
+ debugStatus = FRESH
+ }
+
+ @Synchronized
+ private fun acknowledgeShutdownIfNeeded() {
+ if (!isShutdownRequested) return
+ debugStatus = SHUTDOWN_ACK
+ resetAll() // clear queues
+ (this as Object).notifyAll()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
new file mode 100644
index 00000000..5b6adcd2
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.scheduling.*
+import java.util.*
+import kotlin.coroutines.*
+
+/**
+ * Name of the property that defines the maximal number of threads that are used by [Dispatchers.IO] coroutines dispatcher.
+ */
+public const val IO_PARALLELISM_PROPERTY_NAME = "kotlinx.coroutines.io.parallelism"
+
+/**
+ * Groups various implementations of [CoroutineDispatcher].
+ */
+public actual object Dispatchers {
+ /**
+ * The default [CoroutineDispatcher] that is used by all standard builders like
+ * [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc
+ * if no dispatcher nor any other [ContinuationInterceptor] is specified in their context.
+ *
+ * It is backed by a shared pool of threads on JVM. By default, the maximal level of parallelism used
+ * by this dispatcher is equal to the number of CPU cores, but is at least two.
+ * Level of parallelism X guarantees that no more than X tasks can be executed in this dispatcher in parallel.
+ */
+ @JvmStatic
+ public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
+
+ /**
+ * A coroutine dispatcher that is confined to the Main thread operating with UI objects.
+ * This dispatcher can be used either directly or via [MainScope] factory.
+ * Usually such dispatcher is single-threaded.
+ *
+ * Access to this property may throw [IllegalStateException] if no main thread dispatchers are present in the classpath.
+ *
+ * Depending on platform and classpath it can be mapped to different dispatchers:
+ * - On JS and Native it is equivalent of [Default] dispatcher.
+ * - On JVM it is either Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by
+ * [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
+ *
+ * In order to work with `Main` dispatcher, the following artifacts should be added to project runtime dependencies:
+ * - `kotlinx-coroutines-android` for Android Main thread dispatcher
+ * - `kotlinx-coroutines-javafx` for JavaFx Application thread dispatcher
+ * - `kotlinx-coroutines-swing` for Swing EDT dispatcher
+ *
+ * In order to set a custom `Main` dispatcher for testing purposes, add the `kotlinx-coroutines-test` artifact to
+ * project test dependencies.
+ *
+ * Implementation note: [MainCoroutineDispatcher.immediate] is not supported on Native and JS platforms.
+ */
+ @JvmStatic
+ public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
+
+ /**
+ * A coroutine dispatcher that is not confined to any specific thread.
+ * It executes initial continuation of the coroutine in the current call-frame
+ * and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without
+ * mandating any specific threading policy. Nested coroutines launched in this dispatcher form an event-loop to avoid
+ * stack overflows.
+ *
+ * ### Event loop
+ * Event loop semantics is a purely internal concept and have no guarantees on the order of execution
+ * except that all queued coroutines will be executed on the current thread in the lexical scope of the outermost
+ * unconfined coroutine.
+ *
+ * For example, the following code:
+ * ```
+ * withContext(Dispatcher.Unconfined) {
+ * println(1)
+ * withContext(Dispatcher.Unconfined) { // Nested unconfined
+ * println(2)
+ * }
+ * println(3)
+ * }
+ * println("Done")
+ * ```
+ * Can print both "1 2 3" and "1 3 2", this is an implementation detail that can be changed.
+ * But it is guaranteed that "Done" will be printed only when both `withContext` are completed.
+ *
+ *
+ * Note that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption,
+ * but still want to execute it in the current call-frame until its first suspension, then you can use
+ * an optional [CoroutineStart] parameter in coroutine builders like
+ * [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to the
+ * the value of [CoroutineStart.UNDISPATCHED].
+ */
+ @JvmStatic
+ public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
+
+ /**
+ * The [CoroutineDispatcher] that is designed for offloading blocking IO tasks to a shared pool of threads.
+ *
+ * Additional threads in this pool are created and are shutdown on demand.
+ * The number of threads used by this dispatcher is limited by the value of
+ * "`kotlinx.coroutines.io.parallelism`" ([IO_PARALLELISM_PROPERTY_NAME]) system property.
+ * It defaults to the limit of 64 threads or the number of cores (whichever is larger).
+ *
+ * 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.
+ */
+ @JvmStatic
+ public val IO: CoroutineDispatcher = DefaultScheduler.IO
+}
diff --git a/kotlinx-coroutines-core/jvm/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt
new file mode 100644
index 00000000..598f424b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/EventLoop.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+internal actual abstract class EventLoopImplPlatform: EventLoop() {
+ protected abstract val thread: Thread
+
+ protected actual fun unpark() {
+ val thread = thread // atomic read
+ if (Thread.currentThread() !== thread)
+ unpark(thread)
+ }
+
+ protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) {
+ assert { this !== DefaultExecutor } // otherwise default execution was shutdown with tasks in it (cannot be)
+ DefaultExecutor.schedule(now, delayedTask)
+ }
+}
+
+internal class BlockingEventLoop(
+ override val thread: Thread
+) : EventLoopImplBase()
+
+internal actual fun createEventLoop(): EventLoop = BlockingEventLoop(Thread.currentThread())
+
+/**
+ * Processes next event in the current thread's event loop.
+ *
+ * The result of this function is to be interpreted like this:
+ * * `<= 0` -- there are potentially more events for immediate processing;
+ * * `> 0` -- a number of nanoseconds to wait for the next scheduled event;
+ * * [Long.MAX_VALUE] -- no more events or no thread-local event loop.
+ *
+ * Sample usage of this function:
+ *
+ * ```
+ * while (waitingCondition) {
+ * val time = processNextEventInCurrentThread()
+ * LockSupport.parkNanos(time)
+ * }
+ * ```
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public fun processNextEventInCurrentThread(): Long =
+ ThreadLocalEventLoop.currentOrNull()?.processNextEvent() ?: Long.MAX_VALUE \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/Exceptions.kt b/kotlinx-coroutines-core/jvm/src/Exceptions.kt
new file mode 100644
index 00000000..7a8f385e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/Exceptions.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("FunctionName")
+
+package kotlinx.coroutines
+
+/**
+ * This exception gets thrown if an exception is caught while processing [CompletionHandler] invocation for [Job].
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual class CompletionHandlerException actual constructor(
+ message: String,
+ cause: Throwable
+) : RuntimeException(message, cause)
+
+/**
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending.
+ * It indicates _normal_ cancellation of a coroutine.
+ * **It is not printed to console/log by default uncaught exception handler**.
+ * See [CoroutineExceptionHandler]
+*/
+public actual typealias CancellationException = java.util.concurrent.CancellationException
+
+/**
+ * Creates a cancellation exception with a specified message and [cause].
+ */
+@Suppress("FunctionName")
+public actual fun CancellationException(message: String?, cause: Throwable?) : CancellationException =
+ CancellationException(message).apply { initCause(cause) }
+
+/**
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed
+ * without cause, or with a cause or exception that is not [CancellationException]
+ * (see [Job.getCancellationException]).
+ */
+internal actual class JobCancellationException public actual constructor(
+ message: String,
+ cause: Throwable?,
+ @JvmField internal actual val job: Job
+) : CancellationException(message), CopyableThrowable<JobCancellationException> {
+
+ init {
+ if (cause != null) initCause(cause)
+ }
+
+ override fun fillInStackTrace(): Throwable {
+ if (DEBUG) {
+ return super.fillInStackTrace()
+ }
+
+ /*
+ * In non-debug mode we don't want to have a stacktrace on every cancellation/close,
+ * parent job reference is enough. Stacktrace of JCE is not needed most of the time (e.g., it is not logged)
+ * and hurts performance.
+ */
+ return this
+ }
+
+ override fun createCopy(): JobCancellationException? {
+ if (DEBUG) {
+ return JobCancellationException(message!!, this, job)
+ }
+
+ /*
+ * In non-debug mode we don't copy JCE for speed as it does not have the stack trace anyway.
+ */
+ return null
+ }
+
+ override fun toString(): String = "${super.toString()}; job=$job"
+
+ override fun equals(other: Any?): Boolean =
+ other === this ||
+ other is JobCancellationException && other.message == message && other.job == job && other.cause == cause
+ override fun hashCode(): Int =
+ (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) =
+ addSuppressed(other) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt
new file mode 100644
index 00000000..7d7f4ba7
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/Executors.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016-2019 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.io.Closeable
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+/**
+ * [CoroutineDispatcher] that has underlying [Executor] for dispatching tasks.
+ * Instances of [ExecutorCoroutineDispatcher] should be closed by the owner of the dispatcher.
+ *
+ * This class is generally used as a bridge between coroutine-based API and
+ * asynchronous API which requires instance of the [Executor].
+ */
+public abstract class ExecutorCoroutineDispatcher: CoroutineDispatcher(), Closeable {
+ /**
+ * Closes this coroutine dispatcher and shuts down its executor.
+ *
+ * It may throw an exception if this dispatcher is global and cannot be closed.
+ */
+ public abstract override fun close()
+
+ /**
+ * Underlying executor of current [CoroutineDispatcher].
+ */
+ public abstract val executor: Executor
+}
+
+/**
+ * Converts an instance of [ExecutorService] to an implementation of [ExecutorCoroutineDispatcher].
+ */
+@JvmName("from") // this is for a nice Java API, see issue #255
+public fun ExecutorService.asCoroutineDispatcher(): ExecutorCoroutineDispatcher =
+ ExecutorCoroutineDispatcherImpl(this)
+
+/**
+ * Converts an instance of [Executor] to an implementation of [CoroutineDispatcher].
+ */
+@JvmName("from") // this is for a nice Java API, see issue #255
+public fun Executor.asCoroutineDispatcher(): CoroutineDispatcher =
+ (this as? DispatcherExecutor)?.dispatcher ?: ExecutorCoroutineDispatcherImpl(this)
+
+/**
+ * Converts an instance of [CoroutineDispatcher] to an implementation of [Executor].
+ *
+ * It returns the original executor when used on the result of [Executor.asCoroutineDispatcher] extensions.
+ */
+public fun CoroutineDispatcher.asExecutor(): Executor =
+ (this as? ExecutorCoroutineDispatcher)?.executor ?: DispatcherExecutor(this)
+
+private class DispatcherExecutor(@JvmField val dispatcher: CoroutineDispatcher) : Executor {
+ override fun execute(block: Runnable) = dispatcher.dispatch(EmptyCoroutineContext, block)
+ override fun toString(): String = dispatcher.toString()
+}
+
+private class ExecutorCoroutineDispatcherImpl(override val executor: Executor) : ExecutorCoroutineDispatcherBase() {
+ init {
+ initFutureCancellation()
+ }
+}
+
+internal abstract class ExecutorCoroutineDispatcherBase : ExecutorCoroutineDispatcher(), Delay {
+
+ private var removesFutureOnCancellation: Boolean = false
+
+ internal fun initFutureCancellation() {
+ removesFutureOnCancellation = removeFutureOnCancel(executor)
+ }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ try {
+ executor.execute(wrapTask(block))
+ } catch (e: RejectedExecutionException) {
+ unTrackTask()
+ DefaultExecutor.enqueue(block)
+ }
+ }
+
+ /*
+ * 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), timeMillis, TimeUnit.MILLISECONDS)
+ } else {
+ null
+ }
+ // If everything went fine and the scheduling attempt was not rejected -- use it
+ if (future != null) {
+ continuation.cancelFutureOnCancellation(future)
+ return
+ }
+ // Otherwise fallback to default executor
+ DefaultExecutor.scheduleResumeAfterDelay(timeMillis, continuation)
+ }
+
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val future = if (removesFutureOnCancellation) {
+ scheduleBlock(block, timeMillis, TimeUnit.MILLISECONDS)
+ } else {
+ null
+ }
+
+ return if (future != null ) DisposableFutureHandle(future) else DefaultExecutor.invokeOnTimeout(timeMillis, block)
+ }
+
+ private fun scheduleBlock(block: Runnable, time: Long, unit: TimeUnit): ScheduledFuture<*>? {
+ return try {
+ (executor as? ScheduledExecutorService)?.schedule(block, time, unit)
+ } catch (e: RejectedExecutionException) {
+ null
+ }
+ }
+
+ override fun close() {
+ (executor as? ExecutorService)?.shutdown()
+ }
+
+ override fun toString(): String = executor.toString()
+ override fun equals(other: Any?): Boolean = other is ExecutorCoroutineDispatcherBase && other.executor === executor
+ override fun hashCode(): Int = System.identityHashCode(executor)
+}
+
+private class ResumeUndispatchedRunnable(
+ private val dispatcher: CoroutineDispatcher,
+ private val continuation: CancellableContinuation<Unit>
+) : Runnable {
+ override fun run() {
+ with(continuation) { dispatcher.resumeUndispatched(Unit) }
+ }
+}
+
+/**
+ * An implementation of [DisposableHandle] that cancels the specified future on dispose.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+private class DisposableFutureHandle(private val future: Future<*>) : DisposableHandle {
+ override fun dispose() {
+ future.cancel(false)
+ }
+ override fun toString(): String = "DisposableFutureHandle[$future]"
+}
diff --git a/kotlinx-coroutines-core/jvm/src/Future.kt b/kotlinx-coroutines-core/jvm/src/Future.kt
new file mode 100644
index 00000000..19a375d4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/Future.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("JobKt")
+
+package kotlinx.coroutines
+
+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) }
+ * ```
+ *
+ * @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?
+
+/**
+ * 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) }
+ * ```
+ */
+public fun CancellableContinuation<*>.cancelFutureOnCancellation(future: Future<*>) =
+ invokeOnCancellation(handler = CancelFutureOnCancel(future))
+
+private class CancelFutureOnCompletion(
+ job: Job,
+ private val future: Future<*>
+) : JobNode<Job>(job) {
+ 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)
+ }
+ 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)
+ }
+ override fun toString() = "CancelFutureOnCancel[$future]"
+}
diff --git a/kotlinx-coroutines-core/jvm/src/Runnable.kt b/kotlinx-coroutines-core/jvm/src/Runnable.kt
new file mode 100644
index 00000000..2acd5546
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/Runnable.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * A runnable task for [CoroutineDispatcher.dispatch].
+ */
+public actual typealias Runnable = java.lang.Runnable
+
+/**
+ * Creates [Runnable] task instance.
+ */
+@Suppress("FunctionName")
+public actual inline fun Runnable(crossinline block: () -> Unit): Runnable =
+ java.lang.Runnable { block() }
diff --git a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt
new file mode 100644
index 00000000..da2c8550
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.scheduling.*
+
+internal actual typealias SchedulerTask = Task
+
+internal actual typealias SchedulerTaskContext = TaskContext
+
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+internal actual val SchedulerTask.taskContext: SchedulerTaskContext get() = taskContext
+
+@Suppress("NOTHING_TO_INLINE", "EXTENSION_SHADOWED_BY_MEMBER")
+internal actual inline fun SchedulerTaskContext.afterTask() =
+ afterTask()
diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
new file mode 100644
index 00000000..4e8b6cc4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2016-2018 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.*
+
+/**
+ * Defines elements in [CoroutineContext] that are installed into thread context
+ * every time the coroutine with this element in the context is resumed on a thread.
+ *
+ * Implementations of this interface define a type [S] of the thread-local state that they need to store on
+ * resume of a coroutine and restore later on suspend. The infrastructure provides the corresponding storage.
+ *
+ * Example usage looks like this:
+ *
+ * ```
+ * // Appends "name" of a coroutine to a current thread name when coroutine is executed
+ * class CoroutineName(val name: String) : ThreadContextElement<String> {
+ * // declare companion object for a key of this element in coroutine context
+ * companion object Key : CoroutineContext.Key<CoroutineName>
+ *
+ * // provide the key of the corresponding context element
+ * override val key: CoroutineContext.Key<CoroutineName>
+ * get() = Key
+ *
+ * // this is invoked before coroutine is resumed on current thread
+ * override fun updateThreadContext(context: CoroutineContext): String {
+ * val previousName = Thread.currentThread().name
+ * Thread.currentThread().name = "$previousName # $name"
+ * return previousName
+ * }
+ *
+ * // this is invoked after coroutine has suspended on current thread
+ * override fun restoreThreadContext(context: CoroutineContext, oldState: String) {
+ * Thread.currentThread().name = oldState
+ * }
+ * }
+ *
+ * // Usage
+ * launch(Dispatchers.Main + CoroutineName("Progress bar coroutine")) { ... }
+ * ```
+ *
+ * Every time this coroutine is resumed on a thread, UI thread name is updated to
+ * "UI thread original name # Progress bar coroutine" and the thread name is restored to the original one when
+ * this coroutine suspends.
+ *
+ * To use [ThreadLocal] variable within the coroutine use [ThreadLocal.asContextElement][asContextElement] function.
+ */
+public interface ThreadContextElement<S> : CoroutineContext.Element {
+ /**
+ * Updates context of the current thread.
+ * This function is invoked before the coroutine in the specified [context] is resumed in the current thread
+ * when the context of the coroutine this element.
+ * The result of this function is the old value of the thread-local state that will be passed to [restoreThreadContext].
+ * This method should handle its own exceptions and do not rethrow it. Thrown exceptions will leave coroutine which
+ * context is updated in an undefined state and may crash an application.
+ *
+ * @param context the coroutine context.
+ */
+ public fun updateThreadContext(context: CoroutineContext): S
+
+ /**
+ * Restores context of the current thread.
+ * This function is invoked after the coroutine in the specified [context] is suspended in the current thread
+ * if [updateThreadContext] was previously invoked on resume of this coroutine.
+ * The value of [oldState] is the result of the previous invocation of [updateThreadContext] and it should
+ * be restored in the thread-local state by this function.
+ * This method should handle its own exceptions and do not rethrow it. Thrown exceptions will leave coroutine which
+ * context is updated in an undefined state and may crash an application.
+ *
+ * @param context the coroutine context.
+ * @param oldState the value returned by the previous invocation of [updateThreadContext].
+ */
+ public fun restoreThreadContext(context: CoroutineContext, oldState: S)
+}
+
+/**
+ * Wraps [ThreadLocal] into [ThreadContextElement]. The resulting [ThreadContextElement]
+ * maintains the given [value] of the given [ThreadLocal] for coroutine regardless of the actual thread its is resumed on.
+ * By default [ThreadLocal.get] is used as a value for the thread-local variable, but it can be overridden with [value] parameter.
+ * Beware that context element **does not track** modifications of the thread-local and accessing thread-local from coroutine
+ * without the corresponding context element returns **undefined** value. See the examples for a detailed description.
+ *
+ *
+ * Example usage:
+ * ```
+ * val myThreadLocal = ThreadLocal<String?>()
+ * ...
+ * println(myThreadLocal.get()) // Prints "null"
+ * launch(Dispatchers.Default + myThreadLocal.asContextElement(value = "foo")) {
+ * println(myThreadLocal.get()) // Prints "foo"
+ * withContext(Dispatchers.Main) {
+ * println(myThreadLocal.get()) // Prints "foo", but it's on UI thread
+ * }
+ * }
+ * println(myThreadLocal.get()) // Prints "null"
+ * ```
+ *
+ * The context element does not track modifications of the thread-local variable, for example:
+ *
+ * ```
+ * myThreadLocal.set("main")
+ * withContext(Dispatchers.Main) {
+ * println(myThreadLocal.get()) // Prints "main"
+ * myThreadLocal.set("UI")
+ * }
+ * println(myThreadLocal.get()) // Prints "main", not "UI"
+ * ```
+ *
+ * Use `withContext` to update the corresponding thread-local variable to a different value, for example:
+ * ```
+ * withContext(myThreadLocal.asContextElement("foo")) {
+ * println(myThreadLocal.get()) // Prints "foo"
+ * }
+ * ```
+ *
+ * Accessing the thread-local without corresponding context element leads to undefined value:
+ * ```
+ * val tl = ThreadLocal.withInitial { "initial" }
+ *
+ * runBlocking {
+ * println(tl.get()) // Will print "initial"
+ * // Change context
+ * withContext(tl.asContextElement("modified")) {
+ * println(tl.get()) // Will print "modified"
+ * }
+ * // Context is changed again
+ * println(tl.get()) // <- WARN: can print either "modified" or "initial"
+ * }
+ * ```
+ * to fix this behaviour use `runBlocking(tl.asContextElement())`
+ */
+public fun <T> ThreadLocal<T>.asContextElement(value: T = get()): ThreadContextElement<T> =
+ ThreadLocalElement(value, this)
+
+/**
+ * Return `true` when current thread local is present in the coroutine context, `false` otherwise.
+ * Thread local can be present in the context only if it was added via [asContextElement] to the context.
+ *
+ * Example of usage:
+ * ```
+ * suspend fun processRequest() {
+ * if (traceCurrentRequestThreadLocal.isPresent()) { // Probabilistic tracing
+ * // Do some heavy-weight tracing
+ * }
+ * // Process request regularly
+ * }
+ * ```
+ */
+public suspend inline fun ThreadLocal<*>.isPresent(): Boolean = coroutineContext[ThreadLocalKey(this)] !== null
+
+/**
+ * Checks whether current thread local is present in the coroutine context and throws [IllegalStateException] if it is not.
+ * It is a good practice to validate that thread local is present in the context, especially in large code-bases,
+ * to avoid stale thread-local values and to have a strict invariants.
+ *
+ * E.g. one may use the following method to enforce proper use of the thread locals with coroutines:
+ * ```
+ * public suspend inline fun <T> ThreadLocal<T>.getSafely(): T {
+ * ensurePresent()
+ * return get()
+ * }
+ *
+ * // Usage
+ * withContext(...) {
+ * val value = threadLocal.getSafely() // Fail-fast in case of improper context
+ * }
+ * ```
+ */
+public suspend inline fun ThreadLocal<*>.ensurePresent(): Unit =
+ check(isPresent()) { "ThreadLocal $this is missing from context $coroutineContext" }
diff --git a/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt
new file mode 100644
index 00000000..ce2f81c6
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016-2018 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.
+ * **NOTE: The resulting [ExecutorCoroutineDispatcher] owns native resources (its thread).
+ * Resources are reclaimed by [ExecutorCoroutineDispatcher.close].**
+ *
+ * **NOTE: This API will be replaced in the future**. A different API to create thread-limited thread pools
+ * that is based on a shared thread-pool and does not require the resulting dispatcher to be explicitly closed
+ * will be provided, thus avoiding potential thread leaks and also significantly improving performance, due
+ * to coroutine-oriented scheduling policy and thread-switch minimization.
+ * See [issue #261](https://github.com/Kotlin/kotlinx.coroutines/issues/261) for details.
+ * If you need a completely separate thread-pool with scheduling policy that is based on the standard
+ * JDK executors, use the following expression:
+ * `Executors.newSingleThreadExecutor().asCoroutineDispatcher()`.
+ * See [Executor.asCoroutineDispatcher] for details.
+ *
+ * @param name the base name of the created thread.
+ */
+@ObsoleteCoroutinesApi
+fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher =
+ newFixedThreadPoolContext(1, name)
+
+/**
+ * Creates a coroutine execution context with the fixed-size thread-pool and built-in [yield] support.
+ * **NOTE: The resulting [ExecutorCoroutineDispatcher] owns native resources (its threads).
+ * Resources are reclaimed by [ExecutorCoroutineDispatcher.close].**
+ *
+ * **NOTE: This API will be replaced in the future**. A different API to create thread-limited thread pools
+ * that is based on a shared thread-pool and does not require the resulting dispatcher to be explicitly closed
+ * will be provided, thus avoiding potential thread leaks and also significantly improving performance, due
+ * to coroutine-oriented scheduling policy and thread-switch minimization.
+ * See [issue #261](https://github.com/Kotlin/kotlinx.coroutines/issues/261) for details.
+ * If you need a completely separate thread-pool with scheduling policy that is based on the standard
+ * JDK executors, use the following expression:
+ * `Executors.newFixedThreadPool().asCoroutineDispatcher()`.
+ * See [Executor.asCoroutineDispatcher] for details.
+ *
+ * @param nThreads the number of threads.
+ * @param name the base name of the created threads.
+ */
+@ObsoleteCoroutinesApi
+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()
+ }
+
+ /**
+ * 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]"
+}
diff --git a/kotlinx-coroutines-core/jvm/src/TimeSource.kt b/kotlinx-coroutines-core/jvm/src/TimeSource.kt
new file mode 100644
index 00000000..99a0ca45
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/TimeSource.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// Need InlineOnly for efficient bytecode on Android
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "NOTHING_TO_INLINE")
+
+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)
+}
+
+// For tests only
+// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
+internal var timeSource: TimeSource? = null
+
+@InlineOnly
+internal inline fun currentTimeMillis(): Long =
+ timeSource?.currentTimeMillis() ?: System.currentTimeMillis()
+
+@InlineOnly
+internal actual inline fun nanoTime(): Long =
+ timeSource?.nanoTime() ?: System.nanoTime()
+
+@InlineOnly
+internal inline fun wrapTask(block: Runnable): Runnable =
+ timeSource?.wrapTask(block) ?: block
+
+@InlineOnly
+internal inline fun trackTask() {
+ timeSource?.trackTask()
+}
+
+@InlineOnly
+internal inline fun unTrackTask() {
+ timeSource?.unTrackTask()
+}
+
+@InlineOnly
+internal inline fun registerTimeLoopThread() {
+ timeSource?.registerTimeLoopThread()
+}
+
+@InlineOnly
+internal inline fun unregisterTimeLoopThread() {
+ timeSource?.unregisterTimeLoopThread()
+}
+
+@InlineOnly
+internal inline fun parkNanos(blocker: Any, nanos: Long) {
+ timeSource?.parkNanos(blocker, nanos) ?: LockSupport.parkNanos(blocker, nanos)
+}
+
+@InlineOnly
+internal inline fun unpark(thread: Thread) {
+ timeSource?.unpark(thread) ?: LockSupport.unpark(thread)
+}
diff --git a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
new file mode 100644
index 00000000..ffabb99d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+
+/**
+ * Scope for [actor][GlobalScope.actor] coroutine builder.
+ *
+ * **Note: This API will become obsolete in future updates with introduction of complex actors.**
+ * See [issue #87](https://github.com/Kotlin/kotlinx.coroutines/issues/87).
+ */
+@ObsoleteCoroutinesApi
+public interface ActorScope<E> : CoroutineScope, ReceiveChannel<E> {
+ /**
+ * A reference to the mailbox channel that this coroutine [receives][receive] messages from.
+ * It is provided for convenience, so that the code in the coroutine can refer
+ * to the channel as `channel` as apposed to `this`.
+ * All the [ReceiveChannel] functions on this interface delegate to
+ * the channel instance returned by this function.
+ */
+ val channel: Channel<E>
+}
+
+/**
+ * Launches new coroutine that is receiving messages from its mailbox channel
+ * and returns a reference to its mailbox channel as a [SendChannel]. The resulting
+ * object can be used to [send][SendChannel.send] messages to this coroutine.
+ *
+ * The scope of the coroutine contains [ActorScope] interface, which implements
+ * both [CoroutineScope] and [ReceiveChannel], so that coroutine can invoke
+ * [receive][ReceiveChannel.receive] directly. The channel is [closed][SendChannel.close]
+ * when the coroutine completes.
+ *
+ * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
+ * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
+ * with corresponding [coroutineContext] element.
+ *
+ * By default, the coroutine is immediately scheduled for execution.
+ * Other options can be specified via `start` parameter. See [CoroutineStart] for details.
+ * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,
+ * it will be started implicitly on the first message
+ * [sent][SendChannel.send] to this actors's mailbox channel.
+ *
+ * Uncaught exceptions in this coroutine close the channel with this exception as a cause and
+ * the resulting channel becomes _failed_, so that any attempt to send to such a channel throws exception.
+ *
+ * The kind of the resulting channel depends on the specified [capacity] parameter.
+ * See [Channel] interface documentation for details.
+ *
+ * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
+ *
+ * ### Using actors
+ *
+ * A typical usage of the actor builder looks like this:
+ *
+ * ```
+ * val c = actor {
+ * // initialize actor's state
+ * for (msg in channel) {
+ * // process message here
+ * }
+ * }
+ * // send messages to the actor
+ * c.send(...)
+ * ...
+ * // stop the actor when it is no longer needed
+ * c.close()
+ * ```
+ *
+ * ### Stopping and cancelling actors
+ *
+ * When the inbox channel of the actor is [closed][SendChannel.close] it sends a special "close token" to the actor.
+ * The actor still processes all the messages that were already sent and then "`for (msg in channel)`" loop terminates
+ * and the actor completes.
+ *
+ * If the actor needs to be aborted without processing all the messages that were already sent to it, then
+ * it shall be created with a parent job:
+ *
+ * ```
+ * val job = Job()
+ * val c = actor(context = job) { ... }
+ * ...
+ * // abort the actor
+ * job.cancel()
+ * ```
+ *
+ * When actor's parent job is [cancelled][Job.cancel], then actor's job becomes cancelled. It means that
+ * "`for (msg in channel)`" and other cancellable suspending functions throw [CancellationException] and actor
+ * completes without processing remaining messages.
+ *
+ * **Note: This API will become obsolete in future updates with introduction of complex actors.**
+ * See [issue #87](https://github.com/Kotlin/kotlinx.coroutines/issues/87).
+ *
+ * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
+ * @param capacity capacity of the channel's buffer (no buffer by default).
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param onCompletion optional completion handler for the actor coroutine (see [Job.invokeOnCompletion])
+ * @param block the coroutine code.
+ */
+@ObsoleteCoroutinesApi
+public fun <E> CoroutineScope.actor(
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = 0, // todo: Maybe Channel.DEFAULT here?
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ onCompletion: CompletionHandler? = null,
+ block: suspend ActorScope<E>.() -> Unit
+): SendChannel<E> {
+ val newContext = newCoroutineContext(context)
+ val channel = Channel<E>(capacity)
+ val coroutine = if (start.isLazy)
+ LazyActorCoroutine(newContext, channel, block) else
+ ActorCoroutine(newContext, channel, active = true)
+ if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion)
+ coroutine.start(start, coroutine, block)
+ return coroutine
+}
+
+private open class ActorCoroutine<E>(
+ parentContext: CoroutineContext,
+ channel: Channel<E>,
+ active: Boolean
+) : ChannelCoroutine<E>(parentContext, channel, active), ActorScope<E> {
+
+ override fun onCancelling(cause: Throwable?) {
+ _channel.cancel(cause?.let {
+ it as? CancellationException ?: CancellationException("$classSimpleName was cancelled", it)
+ })
+ }
+
+ override fun handleJobException(exception: Throwable): Boolean {
+ handleCoroutineException(context, exception)
+ return true
+ }
+}
+
+private class LazyActorCoroutine<E>(
+ parentContext: CoroutineContext,
+ channel: Channel<E>,
+ private val block: suspend ActorScope<E>.() -> Unit
+) : ActorCoroutine<E>(parentContext, channel, active = false),
+ SelectClause2<E, SendChannel<E>> {
+ override fun onStart() {
+ block.startCoroutineCancellable(this, this)
+ }
+
+ override suspend fun send(element: E) {
+ start()
+ return super.send(element)
+ }
+
+ override fun offer(element: E): Boolean {
+ start()
+ return super.offer(element)
+ }
+
+ override fun close(cause: Throwable?): Boolean {
+ start()
+ return super.close(cause)
+ }
+
+ override val onSend: SelectClause2<E, SendChannel<E>>
+ get() = this
+
+ // registerSelectSend
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, param: E, block: suspend (SendChannel<E>) -> R) {
+ start()
+ super.onSend.registerSelectClause2(select, param, block)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/channels/Channels.kt b/kotlinx-coroutines-core/jvm/src/channels/Channels.kt
new file mode 100644
index 00000000..78889e70
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/channels/Channels.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("ChannelsKt")
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+
+// -------- Operations on SendChannel --------
+
+/**
+ * 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).
+ *
+ * This is a way to call [Channel.send] method inside a blocking code using [runBlocking],
+ * so this function should not be used from coroutine.
+ */
+public fun <E> SendChannel<E>.sendBlocking(element: E) {
+ // fast path
+ if (offer(element))
+ return
+ // slow path
+ runBlocking {
+ send(element)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt b/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt
new file mode 100644
index 00000000..4bbf77d6
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.coroutines.*
+
+/**
+ * Mode for [ticker] function.
+ *
+ * **Note: Ticker channels are not currently integrated with structured concurrency and their api will change in the future.**
+ */
+@ObsoleteCoroutinesApi
+enum class TickerMode {
+ /**
+ * Adjust delay to maintain fixed period if consumer cannot keep up or is otherwise slow.
+ * **This is a default mode.**
+ *
+ * ```
+ * val channel = ticker(delay = 100)
+ * delay(350) // 250 ms late
+ * println(channel.poll()) // prints Unit
+ * println(channel.poll()) // prints null
+ *
+ * delay(50)
+ * println(channel.poll()) // prints Unit, delay was adjusted
+ * delay(50)
+ * println(channel.poll()) // prints null, we'are not late relatively to previous element
+ * ```
+ */
+ FIXED_PERIOD,
+
+ /**
+ * Maintains fixed delay between produced elements if consumer cannot keep up or it otherwise slow.
+ */
+ FIXED_DELAY
+}
+
+/**
+ * Creates a channel that produces the first item after the given initial delay and subsequent items with the
+ * given delay between them.
+ *
+ * The resulting channel is a _rendezvous channel_. When receiver from this channel does not keep
+ * up with receiving the elements from this channel, they are not being sent due to backpressure. The actual
+ * timing behavior of ticker in this case is controlled by [mode] parameter which
+ * is set to [TickerMode.FIXED_PERIOD] by default. See [TickerMode] for other details.
+ *
+ * This channel stops producing elements immediately after [ReceiveChannel.cancel] invocation.
+ *
+ * **Note** producer to this channel is dispatched via [Dispatchers.Unconfined] by default and started eagerly.
+ *
+ * **Note: Ticker channels are not currently integrated with structured concurrency and their api will change in the future.**
+ *
+ * @param delayMillis delay between each element in milliseconds.
+ * @param initialDelayMillis delay after which the first element will be produced (it is equal to [delayMillis] by default) in milliseconds.
+ * @param context context of the producing coroutine.
+ * @param mode specifies behavior when elements are not received ([FIXED_PERIOD][TickerMode.FIXED_PERIOD] by default).
+ */
+@ObsoleteCoroutinesApi
+public fun ticker(
+ delayMillis: Long,
+ initialDelayMillis: Long = delayMillis,
+ context: CoroutineContext = EmptyCoroutineContext,
+ mode: TickerMode = TickerMode.FIXED_PERIOD
+): ReceiveChannel<Unit> {
+ require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" }
+ require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" }
+ return GlobalScope.produce(Dispatchers.Unconfined + context, capacity = 0) {
+ when (mode) {
+ TickerMode.FIXED_PERIOD -> fixedPeriodTicker(delayMillis, initialDelayMillis, channel)
+ TickerMode.FIXED_DELAY -> fixedDelayTicker(delayMillis, initialDelayMillis, channel)
+ }
+ }
+}
+
+private suspend fun fixedPeriodTicker(
+ delayMillis: Long,
+ initialDelayMillis: Long,
+ channel: SendChannel<Unit>
+) {
+ var deadline = nanoTime() + delayToNanos(initialDelayMillis)
+ delay(initialDelayMillis)
+ val delayNs = delayToNanos(delayMillis)
+ while (true) {
+ deadline += delayNs
+ channel.send(Unit)
+ val now = nanoTime()
+ val nextDelay = (deadline - now).coerceAtLeast(0)
+ if (nextDelay == 0L && delayNs != 0L) {
+ val adjustedDelay = delayNs - (now - deadline) % delayNs
+ deadline = now + adjustedDelay
+ delay(delayNanosToMillis(adjustedDelay))
+ } else {
+ delay(delayNanosToMillis(nextDelay))
+ }
+ }
+}
+
+private suspend fun fixedDelayTicker(
+ delayMillis: Long,
+ initialDelayMillis: Long,
+ channel: SendChannel<Unit>
+) {
+ delay(initialDelayMillis)
+ while (true) {
+ channel.send(Unit)
+ delay(delayMillis)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
new file mode 100644
index 00000000..d8d4d21e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+
+internal actual class AbortFlowException : CancellationException("Flow was aborted, no more elements needed") {
+ override fun fillInStackTrace(): Throwable {
+ if (DEBUG) super.fillInStackTrace()
+ return this
+ }
+}
+
+internal actual class ChildCancelledException : CancellationException("Child of the scoped flow was cancelled") {
+ override fun fillInStackTrace(): Throwable {
+ if (DEBUG) super.fillInStackTrace()
+ return this
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt
new file mode 100644
index 00000000..e835d343
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import java.lang.reflect.*
+import java.util.*
+import java.util.concurrent.*
+import kotlin.concurrent.withLock as withLockJvm
+
+internal actual fun <E> subscriberList(): SubscribersList<E> = CopyOnWriteArrayList<E>()
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock
+
+internal actual inline fun <T> ReentrantLock.withLock(action: () -> T) = this.withLockJvm(action)
+
+internal actual fun <E> identitySet(expectedSize: Int): MutableSet<E> = Collections.newSetFromMap(IdentityHashMap(expectedSize))
+
+private val REMOVE_FUTURE_ON_CANCEL: Method? = try {
+ ScheduledThreadPoolExecutor::class.java.getMethod("setRemoveOnCancelPolicy", Boolean::class.java)
+} catch (e: Throwable) {
+ null
+}
+
+@Suppress("NAME_SHADOWING")
+internal fun removeFutureOnCancel(executor: Executor): Boolean {
+ try {
+ val executor = executor as? ScheduledExecutorService ?: return false
+ (REMOVE_FUTURE_ON_CANCEL ?: return false).invoke(executor, true)
+ return true
+ } catch (e: Throwable) {
+ return true
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt
new file mode 100644
index 00000000..8d11c6fd
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import java.lang.reflect.*
+import java.util.*
+import java.util.concurrent.locks.*
+import kotlin.concurrent.*
+
+private val throwableFields = Throwable::class.java.fieldsCountOrDefault(-1)
+private val cacheLock = ReentrantReadWriteLock()
+private typealias Ctor = (Throwable) -> Throwable?
+// Replace it with ClassValue when Java 6 support is over
+private val exceptionCtors: WeakHashMap<Class<out Throwable>, Ctor> = WeakHashMap()
+
+@Suppress("UNCHECKED_CAST")
+internal fun <E : Throwable> tryCopyException(exception: E): E? {
+ // Fast path for CopyableThrowable
+ if (exception is CopyableThrowable<*>) {
+ return runCatching { exception.createCopy() as E? }.getOrNull()
+ }
+ // Use cached ctor if found
+ cacheLock.read { exceptionCtors[exception.javaClass] }?.let { cachedCtor ->
+ return cachedCtor(exception) as E?
+ }
+ /*
+ * Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
+ */
+ if (throwableFields != exception.javaClass.fieldsCountOrDefault(0)) {
+ cacheLock.write { exceptionCtors[exception.javaClass] = { null } }
+ return null
+ }
+ /*
+ * Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message).
+ * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
+ */
+ var ctor: Ctor? = null
+ val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size }
+ for (constructor in constructors) {
+ ctor = createConstructor(constructor)
+ if (ctor != null) break
+ }
+ // Store the resulting ctor to cache
+ cacheLock.write { exceptionCtors[exception.javaClass] = ctor ?: { null } }
+ return ctor?.invoke(exception) as E?
+}
+
+private fun createConstructor(constructor: Constructor<*>): Ctor? {
+ val p = constructor.parameterTypes
+ return when (p.size) {
+ 2 -> when {
+ p[0] == String::class.java && p[1] == Throwable::class.java ->
+ safeCtor { e -> constructor.newInstance(e.message, e) as Throwable }
+ else -> null
+ }
+ 1 -> when (p[0]) {
+ Throwable::class.java ->
+ safeCtor { e -> constructor.newInstance(e) as Throwable }
+ String::class.java ->
+ safeCtor { e -> (constructor.newInstance(e.message) as Throwable).also { it.initCause(e) } }
+ else -> null
+ }
+ 0 -> safeCtor { e -> (constructor.newInstance() as Throwable).also { it.initCause(e) } }
+ else -> null
+ }
+}
+
+private inline fun safeCtor(crossinline block: (Throwable) -> Throwable): Ctor =
+ { e -> runCatching { block(e) }.getOrNull() }
+
+private fun Class<*>.fieldsCountOrDefault(defaultValue: Int) = kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)
+
+private tailrec fun Class<*>.fieldsCount(accumulator: Int = 0): Int {
+ val fieldsCount = declaredFields.count { !Modifier.isStatic(it.modifiers) }
+ val totalFields = accumulator + fieldsCount
+ val superClass = superclass ?: return totalFields
+ return superClass.fieldsCount(totalFields)
+}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt b/kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt
new file mode 100644
index 00000000..f512bb31
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt
@@ -0,0 +1,98 @@
+package kotlinx.coroutines.internal
+
+import java.io.*
+import java.net.*
+import java.util.*
+import java.util.jar.*
+import java.util.zip.*
+
+/**
+ * A simplified version of [ServiceLoader].
+ * FastServiceLoader locates and instantiates all service providers named in configuration
+ * files placed in the resource directory <tt>META-INF/services</tt>.
+ *
+ * The main difference between this class and classic service loader is in skipping
+ * verification JARs. A verification requires reading the whole JAR (and it causes problems and ANRs on Android devices)
+ * and prevents only trivial checksum issues. See #878.
+ *
+ * If any error occurs during loading, it fallbacks to [ServiceLoader], mostly to prevent R8 issues.
+ */
+internal object FastServiceLoader {
+ private const val PREFIX: String = "META-INF/services/"
+
+ internal fun <S> load(service: Class<S>, loader: ClassLoader): List<S> {
+ return try {
+ loadProviders(service, loader)
+ } catch (e: Throwable) {
+ // Fallback to default service loader
+ ServiceLoader.load(service, loader).toList()
+ }
+ }
+
+ // Visible for tests
+ internal fun <S> loadProviders(service: Class<S>, loader: ClassLoader): List<S> {
+ val fullServiceName = PREFIX + service.name
+ // Filter out situations when both JAR and regular files are in the classpath (e.g. IDEA)
+ val urls = loader.getResources(fullServiceName)
+ val providers = urls.toList().flatMap { parse(it) }.toSet()
+ require(providers.isNotEmpty()) { "No providers were loaded with FastServiceLoader" }
+ return providers.map { getProviderInstance(it, loader, service) }
+ }
+
+ private fun <S> getProviderInstance(name: String, loader: ClassLoader, service: Class<S>): S {
+ val clazz = Class.forName(name, false, loader)
+ require(service.isAssignableFrom(clazz)) { "Expected service of class $service, but found $clazz" }
+ return service.cast(clazz.getDeclaredConstructor().newInstance())
+ }
+
+ private fun parse(url: URL): List<String> {
+ val path = url.toString()
+ // Fast-path for JARs
+ if (path.startsWith("jar")) {
+ val pathToJar = path.substringAfter("jar:file:").substringBefore('!')
+ val entry = path.substringAfter("!/")
+ // mind the verify = false flag!
+ (JarFile(pathToJar, false)).use { file ->
+ BufferedReader(InputStreamReader(file.getInputStream(ZipEntry(entry)), "UTF-8")).use { r ->
+ return parseFile(r)
+ }
+ }
+ }
+ // Regular path for everything else
+ return BufferedReader(InputStreamReader(url.openStream())).use { reader ->
+ parseFile(reader)
+ }
+ }
+
+ // JarFile does no implement Closesable on Java 1.6
+ private inline fun <R> JarFile.use(block: (JarFile) -> R): R {
+ var cause: Throwable? = null
+ try {
+ return block(this)
+ } catch (e: Throwable) {
+ cause = e
+ throw e
+ } finally {
+ try {
+ close()
+ } catch (closeException: Throwable) {
+ if (cause === null) throw closeException
+ cause.addSuppressed(closeException)
+ throw cause
+ }
+ }
+ }
+
+ private fun parseFile(r: BufferedReader): List<String> {
+ val names = mutableSetOf<String>()
+ while (true) {
+ val line = r.readLine() ?: break
+ val serviceName = line.substringBefore("#").trim()
+ require(serviceName.all { it == '.' || Character.isJavaIdentifierPart(it) }) { "Illegal service provider class name: $serviceName" }
+ if (serviceName.isNotEmpty()) {
+ names.add(serviceName)
+ }
+ }
+ return names.toList()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
new file mode 100644
index 00000000..d3d168a4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
@@ -0,0 +1,678 @@
+/*
+ * Copyright 2016-2019 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.*
+
+private typealias Node = LockFreeLinkedListNode
+
+@PublishedApi
+internal const val UNDECIDED = 0
+
+@PublishedApi
+internal const val SUCCESS = 1
+
+@PublishedApi
+internal const val FAILURE = 2
+
+@PublishedApi
+internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE")
+
+@PublishedApi
+internal val ALREADY_REMOVED: Any = Symbol("ALREADY_REMOVED")
+
+@PublishedApi
+internal val LIST_EMPTY: Any = Symbol("LIST_EMPTY")
+
+private val REMOVE_PREPARED: Any = Symbol("REMOVE_PREPARED")
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual typealias RemoveFirstDesc<T> = LockFreeLinkedListNode.RemoveFirstDesc<T>
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual typealias AddLastDesc<T> = LockFreeLinkedListNode.AddLastDesc<T>
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual typealias AbstractAtomicDesc = LockFreeLinkedListNode.AbstractAtomicDesc
+
+/**
+ * Doubly-linked concurrent list node with remove support.
+ * Based on paper
+ * ["Lock-Free and Practical Doubly Linked List-Based Deques Using Single-Word Compare-and-Swap"](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.140.4693&rep=rep1&type=pdf)
+ * by Sundell and Tsigas.
+ *
+ * Important notes:
+ * * The instance of this class serves both as list head/tail sentinel and as the list item.
+ * Sentinel node should be never removed.
+ * * There are no operations to add items to left side of the list, only to the end (right side), because we cannot
+ * efficiently linearize them with atomic multi-step head-removal operations. In short,
+ * support for [describeRemoveFirst] operation precludes ability to add items at the beginning.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+@Suppress("LeakingThis")
+@InternalCoroutinesApi
+public actual open class LockFreeLinkedListNode {
+ private val _next = atomic<Any>(this) // Node | Removed | OpDescriptor
+ private val _prev = atomic<Any>(this) // Node | Removed
+ private val _removedRef = atomic<Removed?>(null) // lazily cached removed ref to this
+
+ private fun removed(): Removed =
+ _removedRef.value ?: Removed(this).also { _removedRef.lazySet(it) }
+
+ @PublishedApi
+ internal abstract class CondAddOp(
+ @JvmField val newNode: Node
+ ) : AtomicOp<Node>() {
+ @JvmField var oldNext: Node? = null
+
+ override fun complete(affected: Node, failure: Any?) {
+ val success = failure == null
+ val update = if (success) newNode else oldNext
+ if (update != null && affected._next.compareAndSet( this, update)) {
+ // only the thread the makes this update actually finishes add operation
+ if (success) newNode.finishAdd(oldNext!!)
+ }
+ }
+ }
+
+ @PublishedApi
+ internal inline fun makeCondAddOp(node: Node, crossinline condition: () -> Boolean): CondAddOp =
+ object : CondAddOp(node) {
+ override fun prepare(affected: Node): Any? = if (condition()) null else CONDITION_FALSE
+ }
+
+ public actual val isRemoved: Boolean get() = next is Removed
+
+ // LINEARIZABLE. Returns Node | Removed
+ public val next: Any get() {
+ _next.loop { next ->
+ if (next !is OpDescriptor) return next
+ next.perform(this)
+ }
+ }
+
+ // LINEARIZABLE. Returns next non-removed Node
+ public actual val nextNode: Node get() = next.unwrap()
+
+ // LINEARIZABLE. Returns Node | Removed
+ public val prev: Any get() {
+ _prev.loop { prev ->
+ if (prev is Removed) return prev
+ prev as Node // otherwise, it can be only node
+ if (prev.next === this) return prev
+ correctPrev(prev, null)
+ }
+ }
+
+ // LINEARIZABLE. Returns prev non-removed Node
+ public actual val prevNode: Node get() = prev.unwrap()
+
+ // ------ addOneIfEmpty ------
+
+ public actual fun addOneIfEmpty(node: Node): Boolean {
+ node._prev.lazySet(this)
+ node._next.lazySet(this)
+ while (true) {
+ val next = next
+ if (next !== this) return false // this is not an empty list!
+ if (_next.compareAndSet(this, node)) {
+ // added successfully (linearized add) -- fixup the list
+ node.finishAdd(this)
+ return true
+ }
+ }
+ }
+
+ // ------ addLastXXX ------
+
+ /**
+ * Adds last item to this list.
+ */
+ public actual fun addLast(node: Node) {
+ while (true) { // lock-free loop on prev.next
+ val prev = prev as Node // sentinel node is never removed, so prev is always defined
+ if (prev.addNext(node, this)) return
+ }
+ }
+
+ public fun <T : Node> describeAddLast(node: T): AddLastDesc<T> = AddLastDesc(this, node)
+
+ /**
+ * Adds last item to this list atomically if the [condition] is true.
+ */
+ public actual inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean {
+ val condAdd = makeCondAddOp(node, condition)
+ while (true) { // lock-free loop on prev.next
+ val prev = prev as Node // sentinel node is never removed, so prev is always defined
+ when (prev.tryCondAddNext(node, this, condAdd)) {
+ SUCCESS -> return true
+ FAILURE -> return false
+ }
+ }
+ }
+
+ public actual inline fun addLastIfPrev(node: Node, predicate: (Node) -> Boolean): Boolean {
+ while (true) { // lock-free loop on prev.next
+ val prev = prev as Node // sentinel node is never removed, so prev is always defined
+ if (!predicate(prev)) return false
+ if (prev.addNext(node, this)) return true
+ }
+ }
+
+ public actual inline fun addLastIfPrevAndIf(
+ node: Node,
+ predicate: (Node) -> Boolean, // prev node predicate
+ crossinline condition: () -> Boolean // atomically checked condition
+ ): Boolean {
+ val condAdd = makeCondAddOp(node, condition)
+ while (true) { // lock-free loop on prev.next
+ val prev = prev as Node // sentinel node is never removed, so prev is always defined
+ if (!predicate(prev)) return false
+ when (prev.tryCondAddNext(node, this, condAdd)) {
+ SUCCESS -> return true
+ FAILURE -> return false
+ }
+ }
+ }
+
+ // ------ addXXX util ------
+
+ /**
+ * Given:
+ * ```
+ * +-----------------------+
+ * this | node V next
+ * +---+---+ +---+---+ +---+---+
+ * ... <-- | P | N | | P | N | | P | N | --> ....
+ * +---+---+ +---+---+ +---+---+
+ * ^ |
+ * +-----------------------+
+ * ```
+ * Produces:
+ * ```
+ * this node next
+ * +---+---+ +---+---+ +---+---+
+ * ... <-- | P | N | ==> | P | N | --> | P | N | --> ....
+ * +---+---+ +---+---+ +---+---+
+ * ^ | ^ |
+ * +---------+ +---------+
+ * ```
+ * Where `==>` denotes linearization point.
+ * Returns `false` if `next` was not following `this` node.
+ */
+ @PublishedApi
+ internal fun addNext(node: Node, next: Node): Boolean {
+ node._prev.lazySet(this)
+ node._next.lazySet(next)
+ if (!_next.compareAndSet(next, node)) return false
+ // added successfully (linearized add) -- fixup the list
+ node.finishAdd(next)
+ return true
+ }
+
+ // returns UNDECIDED, SUCCESS or FAILURE
+ @PublishedApi
+ internal fun tryCondAddNext(node: Node, next: Node, condAdd: CondAddOp): Int {
+ node._prev.lazySet(this)
+ node._next.lazySet(next)
+ condAdd.oldNext = next
+ if (!_next.compareAndSet(next, condAdd)) return UNDECIDED
+ // added operation successfully (linearized) -- complete it & fixup the list
+ return if (condAdd.perform(this) == null) SUCCESS else FAILURE
+ }
+
+ // ------ removeXXX ------
+
+ /**
+ * Removes this node from the list. Returns `true` when removed successfully, or `false` if the node was already
+ * removed or if it was not added to any list in the first place.
+ *
+ * **Note**: Invocation of this operation does not guarantee that remove was actually complete if result was `false`.
+ * In particular, invoking [nextNode].[prevNode] might still return this node even though it is "already removed".
+ * Invoke [helpRemove] to make sure that remove was completed.
+ */
+ public actual open fun remove(): Boolean {
+ while (true) { // lock-free loop on next
+ val next = this.next
+ if (next is Removed) return false // was already removed -- don't try to help (original thread will take care)
+ if (next === this) return false // was not even added
+ val removed = (next as Node).removed()
+ if (_next.compareAndSet(next, removed)) {
+ // was removed successfully (linearized remove) -- fixup the list
+ finishRemove(next)
+ return true
+ }
+ }
+ }
+
+ public actual fun helpRemove() {
+ val removed = this.next as? Removed ?: error("Must be invoked on a removed node")
+ finishRemove(removed.ref)
+ }
+
+ public actual fun removeFirstOrNull(): Node? {
+ while (true) { // try to linearize
+ val first = next as Node
+ if (first === this) return null
+ if (first.remove()) return first
+ first.helpDelete() // must help delete, or loose lock-freedom
+ }
+ }
+
+ public fun describeRemoveFirst(): RemoveFirstDesc<Node> = RemoveFirstDesc(this)
+
+ public inline fun <reified T> removeFirstIfIsInstanceOf(): T? {
+ while (true) { // try to linearize
+ val first = next as Node
+ if (first === this) return null
+ if (first !is T) return null
+ if (first.remove()) return first
+ first.helpDelete() // must help delete, or loose lock-freedom
+ }
+ }
+
+ // just peek at item when predicate is true
+ public actual inline fun <reified T> removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? {
+ while (true) { // try to linearize
+ val first = next as Node
+ if (first === this) return null
+ if (first !is T) return null
+ if (predicate(first)) return first // just peek when predicate is true
+ if (first.remove()) return first
+ first.helpDelete() // must help delete, or loose lock-freedom
+ }
+ }
+
+ // ------ multi-word atomic operations helpers ------
+
+ public open class AddLastDesc<T : Node> constructor(
+ @JvmField val queue: Node,
+ @JvmField val node: T
+ ) : AbstractAtomicDesc() {
+ init {
+ // require freshly allocated node here
+ assert { node._next.value === node && node._prev.value === node }
+ }
+
+ final override fun takeAffectedNode(op: OpDescriptor): Node {
+ while (true) {
+ val prev = queue._prev.value as Node // this sentinel node is never removed
+ val next = prev._next.value
+ if (next === queue) return prev // all is good -> linked properly
+ if (next === op) return prev // all is good -> our operation descriptor is already there
+ if (next is OpDescriptor) { // some other operation descriptor -> help & retry
+ next.perform(prev)
+ continue
+ }
+ // linked improperly -- help insert
+ val affected = queue.correctPrev(prev, op)
+ // we can find node which this operation is already affecting while trying to correct prev
+ if (affected != null) return affected
+ }
+ }
+
+ private val _affectedNode = atomic<Node?>(null)
+ final override val affectedNode: Node? get() = _affectedNode.value
+ final override val originalNext: Node? get() = queue
+
+ override fun retry(affected: Node, next: Any): Boolean = next !== queue
+
+ protected override fun onPrepare(affected: Node, next: Node): Any? {
+ // Note: onPrepare must use CAS to make sure the stale invocation is not
+ // going to overwrite the previous decision on successful preparation.
+ // Result of CAS is irrelevant, but we must ensure that it is set when invoker completes
+ _affectedNode.compareAndSet(null, affected)
+ return null // always success
+ }
+
+ override fun updatedNext(affected: Node, next: Node): Any {
+ // it is invoked only on successfully completion of operation, but this invocation can be stale,
+ // so we must use CAS to set both prev & next pointers
+ node._prev.compareAndSet(node, affected)
+ node._next.compareAndSet(node, queue)
+ return node
+ }
+
+ override fun finishOnSuccess(affected: Node, next: Node) {
+ node.finishAdd(queue)
+ }
+ }
+
+ public open class RemoveFirstDesc<T>(
+ @JvmField val queue: Node
+ ) : AbstractAtomicDesc() {
+ private val _affectedNode = atomic<Node?>(null)
+ private val _originalNext = atomic<Node?>(null)
+
+ @Suppress("UNCHECKED_CAST")
+ public val result: T get() = affectedNode!! as T
+
+ final override fun takeAffectedNode(op: OpDescriptor): Node = queue.next as Node
+ final override val affectedNode: Node? get() = _affectedNode.value
+ final override val originalNext: Node? get() = _originalNext.value
+
+ // check node predicates here, must signal failure if affect is not of type T
+ protected override fun failure(affected: Node): Any? =
+ if (affected === queue) LIST_EMPTY else null
+
+ // validate the resulting node (return false if it should be deleted)
+ protected open fun validatePrepared(node: T): Boolean = true // false means remove node & retry
+
+ final override fun retry(affected: Node, next: Any): Boolean {
+ if (next !is Removed) return false
+ affected.helpDelete() // must help delete, or loose lock-freedom
+ return true
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ final override fun onPrepare(affected: Node, next: Node): Any? {
+ assert { affected !is LockFreeLinkedListHead }
+ if (!validatePrepared(affected as T)) return REMOVE_PREPARED
+ // Note: onPrepare must use CAS to make sure the stale invocation is not
+ // going to overwrite the previous decision on successful preparation.
+ // Result of CAS is irrelevant, but we must ensure that it is set when invoker completes
+ _affectedNode.compareAndSet(null, affected)
+ _originalNext.compareAndSet(null, next)
+ return null // ok
+ }
+
+ final override fun updatedNext(affected: Node, next: Node): Any = next.removed()
+ final override fun finishOnSuccess(affected: Node, next: Node) = affected.finishRemove(next)
+ }
+
+ public abstract class AbstractAtomicDesc : AtomicDesc() {
+ protected abstract val affectedNode: Node?
+ protected abstract val originalNext: Node?
+ protected open fun takeAffectedNode(op: OpDescriptor): Node = affectedNode!!
+ protected open fun failure(affected: Node): Any? = null // next: Node | Removed
+ protected open fun retry(affected: Node, next: Any): Boolean = false // next: Node | Removed
+ protected abstract fun onPrepare(affected: Node, next: Node): Any? // non-null on failure
+ protected abstract fun updatedNext(affected: Node, next: Node): Any
+ protected abstract fun finishOnSuccess(affected: Node, next: Node)
+
+ // This is Harris's RDCSS (Restricted Double-Compare Single Swap) operation
+ // It inserts "op" descriptor of when "op" status is still undecided (rolls back otherwise)
+ private class PrepareOp(
+ @JvmField val next: Node,
+ @JvmField val op: AtomicOp<Node>,
+ @JvmField val desc: AbstractAtomicDesc
+ ) : OpDescriptor() {
+ override fun perform(affected: Any?): Any? {
+ affected as Node // type assertion
+ val decision = desc.onPrepare(affected, next)
+ if (decision != null) {
+ if (decision === REMOVE_PREPARED) {
+ // remove element on failure
+ val removed = next.removed()
+ if (affected._next.compareAndSet(this, removed)) {
+ affected.helpDelete()
+ }
+ } else {
+ // some other failure -- mark as decided
+ op.tryDecide(decision)
+ // undo preparations
+ affected._next.compareAndSet(this, next)
+ }
+ return decision
+ }
+ val update: Any = if (op.isDecided) next else op // restore if decision was already reached
+ affected._next.compareAndSet(this, update)
+ return null // ok
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ final override fun prepare(op: AtomicOp<*>): Any? {
+ while (true) { // lock free loop on next
+ val affected = takeAffectedNode(op)
+ // read its original next pointer first
+ val next = affected._next.value
+ // then see if already reached consensus on overall operation
+ if (next === op) return null // already in process of operation -- all is good
+ if (op.isDecided) return null // already decided this operation -- go to next desc
+ if (next is OpDescriptor) {
+ // some other operation is in process -- help it
+ next.perform(affected)
+ continue // and retry
+ }
+ // next: Node | Removed
+ val failure = failure(affected)
+ if (failure != null) return failure // signal failure
+ if (retry(affected, next)) continue // retry operation
+ val prepareOp = PrepareOp(next as Node, op as AtomicOp<Node>, this)
+ if (affected._next.compareAndSet(next, prepareOp)) {
+ // prepared -- complete preparations
+ val prepFail = prepareOp.perform(affected)
+ if (prepFail === REMOVE_PREPARED) continue // retry
+ return prepFail
+ }
+ }
+ }
+
+ final override fun complete(op: AtomicOp<*>, failure: Any?) {
+ val success = failure == null
+ val affectedNode = affectedNode ?: run { assert { !success }; return }
+ val originalNext = originalNext ?: run { assert { !success }; return }
+ val update = if (success) updatedNext(affectedNode, originalNext) else originalNext
+ if (affectedNode._next.compareAndSet(op, update)) {
+ if (success) finishOnSuccess(affectedNode, originalNext)
+ }
+ }
+ }
+
+ // ------ other helpers ------
+
+ /**
+ * Given:
+ * ```
+ *
+ * prev this next
+ * +---+---+ +---+---+ +---+---+
+ * ... <-- | P | N | --> | P | N | --> | P | N | --> ....
+ * +---+---+ +---+---+ +---+---+
+ * ^ ^ | |
+ * | +---------+ |
+ * +-------------------------+
+ * ```
+ * Produces:
+ * ```
+ * prev this next
+ * +---+---+ +---+---+ +---+---+
+ * ... <-- | P | N | --> | P | N | --> | P | N | --> ....
+ * +---+---+ +---+---+ +---+---+
+ * ^ | ^ |
+ * +---------+ +---------+
+ * ```
+ */
+ private fun finishAdd(next: Node) {
+ next._prev.loop { nextPrev ->
+ if (nextPrev is Removed || this.next !== next) return // next was removed, remover fixes up links
+ if (next._prev.compareAndSet(nextPrev, this)) {
+ if (this.next is Removed) {
+ // already removed
+ next.correctPrev(nextPrev as Node, null)
+ }
+ return
+ }
+ }
+ }
+
+ private fun finishRemove(next: Node) {
+ helpDelete()
+ next.correctPrev(_prev.value.unwrap(), null)
+ }
+
+ private fun markPrev(): Node {
+ _prev.loop { prev ->
+ if (prev is Removed) return prev.ref
+ // See detailed comment in findHead on why `prev === this` is a special case for which we know that
+ // the prev should have being pointing to the head of list but finishAdd that was supposed
+ // to do that is not complete yet.
+ val removedPrev = (if (prev === this) findHead() else (prev as Node)).removed()
+ if (_prev.compareAndSet(prev, removedPrev)) return prev
+ }
+ }
+
+ /**
+ * Finds the head of the list (implementing [LockFreeLinkedListHead]) by following [next] pointers.
+ *
+ * The code in [kotlinx.coroutines.JobSupport] performs upgrade of a single node to a list.
+ * It uses [addOneIfEmpty] to add the list head to "empty list of a single node" once.
+ * During upgrade a transient state of the list looks like this:
+ *
+ * ```
+ * +-----------------+
+ * | |
+ * node V head |
+ * +---+---+ +---+---+ |
+ * +-> | P | N | --> | P | N |-+
+ * | +---+---+ +---+---+
+ * | | ^ |
+ * +---- + +---------+
+ * ```
+ *
+ * The [prev] pointer in `node` still points to itself when [finishAdd] (invoked inside [addOneIfEmpty])
+ * has not completed yet. If this state is observed, then we know that [prev] should have been pointing
+ * to the list head. This function is looking up the head by following consistent chain of [next] pointers.
+ */
+ private fun findHead(): Node {
+ var cur = this
+ while (true) {
+ if (cur is LockFreeLinkedListHead) return cur
+ cur = cur.nextNode
+ assert { cur !== this } // "Cannot loop to this while looking for list head"
+ }
+ }
+
+ // fixes next links to the left of this node
+ @PublishedApi
+ internal fun helpDelete() {
+ var last: Node? = null // will set to the node left of prev when found
+ var prev: Node = markPrev()
+ var next: Node = (this._next.value as Removed).ref
+ while (true) {
+ // move to the right until first non-removed node
+ val nextNext = next.next
+ if (nextNext is Removed) {
+ next.markPrev()
+ next = nextNext.ref
+ continue
+ }
+ // move the the left until first non-removed node
+ val prevNext = prev.next
+ if (prevNext is Removed) {
+ if (last != null) {
+ prev.markPrev()
+ last._next.compareAndSet(prev, prevNext.ref)
+ prev = last
+ last = null
+ } else {
+ prev = prev._prev.value.unwrap()
+ }
+ continue
+ }
+ if (prevNext !== this) {
+ // skipped over some removed nodes to the left -- setup to fixup the next links
+ last = prev
+ prev = prevNext as Node
+ if (prev === next) return // already done!!!
+ continue
+ }
+ // Now prev & next are Ok
+ if (prev._next.compareAndSet(this, next)) return // success!
+ }
+ }
+
+ // fixes prev links from this node
+ // returns affected node by this operation when this op is in progress (and nothing can be corrected)
+ // returns null otherwise (prev was corrected)
+ private fun correctPrev(_prev: Node, op: OpDescriptor?): Node? {
+ var prev: Node = _prev
+ var last: Node? = null // will be set so that last.next === prev
+ while (true) {
+ // move the the left until first non-removed node
+ val prevNext = prev._next.value
+ if (prevNext === op) return prev // part of the same op -- don't recurse, didn't correct prev
+ if (prevNext is OpDescriptor) { // help & retry
+ prevNext.perform(prev)
+ continue
+ }
+ if (prevNext is Removed) {
+ if (last !== null) {
+ prev.markPrev()
+ last._next.compareAndSet(prev, prevNext.ref)
+ prev = last
+ last = null
+ } else {
+ prev = prev._prev.value.unwrap()
+ }
+ continue
+ }
+ val oldPrev = this._prev.value
+ if (oldPrev is Removed) return null // this node was removed, too -- its remover will take care
+ if (prevNext !== this) {
+ // need to fixup next
+ last = prev
+ prev = prevNext as Node
+ continue
+ }
+ if (oldPrev === prev) return null // it is already linked as needed
+ if (this._prev.compareAndSet(oldPrev, prev)) {
+ if (prev._prev.value !is Removed) return null // finish only if prev was not concurrently removed
+ }
+ }
+ }
+
+ internal fun validateNode(prev: Node, next: Node) {
+ assert { prev === this._prev.value }
+ assert { next === this._next.value }
+ }
+
+ override fun toString(): String = "${this::class.java.simpleName}@${Integer.toHexString(System.identityHashCode(this))}"
+}
+
+private class Removed(@JvmField val ref: Node) {
+ override fun toString(): String = "Removed[$ref]"
+}
+
+@PublishedApi
+internal fun Any.unwrap(): Node = (this as? Removed)?.ref ?: this as Node
+
+/**
+ * Head (sentinel) item of the linked list that is never removed.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() {
+ public actual val isEmpty: Boolean get() = next === this
+
+ /**
+ * Iterates over all elements in this list of a specified type.
+ */
+ public actual inline fun <reified T : Node> forEach(block: (T) -> Unit) {
+ var cur: Node = next as Node
+ while (cur != this) {
+ if (cur is T) block(cur)
+ cur = cur.nextNode
+ }
+ }
+
+ // just a defensive programming -- makes sure that list head sentinel is never removed
+ public actual final override fun remove(): Boolean = throw UnsupportedOperationException()
+
+ internal fun validate() {
+ var prev: Node = this
+ var cur: Node = next as Node
+ while (cur != this) {
+ val next = cur.nextNode
+ cur.validateNode(prev, next)
+ prev = cur
+ cur = next
+ }
+ validateNode(prev, next as Node)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt
new file mode 100644
index 00000000..63e38cb0
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt
@@ -0,0 +1,114 @@
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import java.util.*
+import kotlin.coroutines.*
+
+/**
+ * Name of the boolean property that enables using of [FastServiceLoader].
+ */
+private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.fast.service.loader"
+
+// Lazy loader for the main dispatcher
+internal object MainDispatcherLoader {
+
+ private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true)
+
+ @JvmField
+ val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()
+
+ private fun loadMainDispatcher(): MainCoroutineDispatcher {
+ return try {
+ val factories = if (FAST_SERVICE_LOADER_ENABLED) {
+ MainDispatcherFactory::class.java.let { clz ->
+ FastServiceLoader.load(clz, clz.classLoader)
+ }
+ } else {
+ //We are explicitly using the
+ //`ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`
+ //form of the ServiceLoader call to enable R8 optimization when compiled on Android.
+ ServiceLoader.load(
+ MainDispatcherFactory::class.java,
+ MainDispatcherFactory::class.java.classLoader
+ ).iterator().asSequence().toList()
+ }
+ factories.maxBy { it.loadPriority }?.tryCreateDispatcher(factories)
+ ?: MissingMainCoroutineDispatcher(null)
+ } catch (e: Throwable) {
+ // Service loader can throw an exception as well
+ MissingMainCoroutineDispatcher(e)
+ }
+ }
+}
+
+/**
+ * If anything goes wrong while trying to create main dispatcher (class not found,
+ * initialization failed, etc), then replace the main dispatcher with a special
+ * stub that throws an error message on any attempt to actually use it.
+ *
+ * @suppress internal API
+ */
+@InternalCoroutinesApi
+public fun MainDispatcherFactory.tryCreateDispatcher(factories: List<MainDispatcherFactory>): MainCoroutineDispatcher =
+ try {
+ createDispatcher(factories)
+ } catch (cause: Throwable) {
+ MissingMainCoroutineDispatcher(cause, hintOnError())
+ }
+
+/** @suppress */
+@InternalCoroutinesApi
+public fun MainCoroutineDispatcher.isMissing(): Boolean = this is MissingMainCoroutineDispatcher
+
+private class MissingMainCoroutineDispatcher(
+ private val cause: Throwable?,
+ private val errorHint: String? = null
+) : MainCoroutineDispatcher(), Delay {
+
+ override val immediate: MainCoroutineDispatcher get() = this
+
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+ missing()
+ }
+
+ override suspend fun delay(time: Long) {
+ missing()
+ }
+
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ missing()
+ }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) =
+ missing()
+
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) =
+ missing()
+
+ private fun missing(): Nothing {
+ if (cause == null) {
+ throw IllegalStateException(
+ "Module with the Main dispatcher is missing. " +
+ "Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android'"
+ )
+ } else {
+ val message = "Module with the Main dispatcher had failed to initialize" + (errorHint?.let { ". $it" } ?: "")
+ throw IllegalStateException(message, cause)
+ }
+ }
+
+ override fun toString(): String = "Main[missing${if (cause != null) ", cause=$cause" else ""}]"
+}
+
+/**
+ * @suppress
+ */
+@InternalCoroutinesApi
+public object MissingMainCoroutineDispatcherFactory : MainDispatcherFactory {
+ override val loadPriority: Int
+ get() = -1
+
+ override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
+ return MissingMainCoroutineDispatcher(null)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt
new file mode 100644
index 00000000..baa96827
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2016-2018 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")
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+import kotlin.coroutines.jvm.internal.probeCoroutineCreated as probe
+
+internal actual inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> = probe(completion)
diff --git a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
new file mode 100644
index 00000000..2d7ed7a3
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UNCHECKED_CAST")
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import java.util.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+/*
+ * `Class.forName(name).canonicalName` instead of plain `name` is required to properly handle
+ * Android's minifier that renames these classes and breaks our recovery heuristic without such lookup.
+ */
+private const val baseContinuationImplClass = "kotlin.coroutines.jvm.internal.BaseContinuationImpl"
+private const val stackTraceRecoveryClass = "kotlinx.coroutines.internal.StackTraceRecoveryKt"
+
+private val baseContinuationImplClassName = runCatching {
+ Class.forName(baseContinuationImplClass).canonicalName
+}.getOrElse { baseContinuationImplClass }
+
+private val stackTraceRecoveryClassName = runCatching {
+ Class.forName(stackTraceRecoveryClass).canonicalName
+}.getOrElse { stackTraceRecoveryClass }
+
+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
+ return copy.sanitizeStackTrace()
+}
+
+private fun <E : Throwable> E.sanitizeStackTrace(): E {
+ val stackTrace = stackTrace
+ val size = stackTrace.size
+ val lastIntrinsic = stackTrace.frameIndex(stackTraceRecoveryClassName)
+ val startIndex = lastIntrinsic + 1
+ val endIndex = stackTrace.frameIndex(baseContinuationImplClassName)
+ val adjustment = if (endIndex == -1) 0 else size - endIndex
+ val trace = Array(size - lastIntrinsic - adjustment) {
+ if (it == 0) {
+ artificialFrame("Coroutine boundary")
+ } else {
+ stackTrace[startIndex + it - 1]
+ }
+ }
+
+ setStackTrace(trace)
+ return this
+}
+
+internal actual fun <E : Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E {
+ if (!RECOVER_STACK_TRACES || continuation !is CoroutineStackFrame) return exception
+ return recoverFromStackFrame(exception, continuation)
+}
+
+private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: CoroutineStackFrame): E {
+ /*
+ * Here we are checking whether exception has already recovered stacktrace.
+ * If so, we extract initial and merge recovered stacktrace and current one
+ */
+ 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
+ val stacktrace = createStackTrace(continuation)
+ if (stacktrace.isEmpty()) return exception
+
+ // Merge if necessary
+ if (cause !== exception) {
+ mergeRecoveredTraces(recoveredStacktrace, stacktrace)
+ }
+
+ // Take recovered stacktrace, merge it with existing one if necessary and return
+ return createFinalException(cause, newException, stacktrace)
+}
+
+/*
+ * Here we partially copy original exception stackTrace to make current one much prettier.
+ * E.g. for
+ * ```
+ * fun foo() = async { error(...) }
+ * suspend fun bar() = foo().await()
+ * ```
+ * we would like to produce following exception:
+ * IllegalStateException
+ * at foo
+ * at kotlin.coroutines.resumeWith
+ * (Coroutine boundary)
+ * at bar
+ * ...real stackTrace...
+ * caused by "IllegalStateException" (original one)
+ */
+private fun <E : Throwable> createFinalException(cause: E, result: E, resultStackTrace: ArrayDeque<StackTraceElement>): E {
+ resultStackTrace.addFirst(artificialFrame("Coroutine boundary"))
+ val causeTrace = cause.stackTrace
+ val size = causeTrace.frameIndex(baseContinuationImplClassName)
+ if (size == -1) {
+ result.stackTrace = resultStackTrace.toTypedArray()
+ return result
+ }
+
+ val mergedStackTrace = arrayOfNulls<StackTraceElement>(resultStackTrace.size + size)
+ for (i in 0 until size) {
+ mergedStackTrace[i] = causeTrace[i]
+ }
+
+ for ((index, element) in resultStackTrace.withIndex()) {
+ mergedStackTrace[size + index] = element
+ }
+
+ result.stackTrace = mergedStackTrace
+ return result
+}
+
+/**
+ * Find initial cause of the exception without restored stacktrace.
+ * Returns intermediate stacktrace as well in order to avoid excess cloning of array as an optimization.
+ */
+private fun <E : Throwable> E.causeAndStacktrace(): Pair<E, Array<StackTraceElement>> {
+ val cause = cause
+ return if (cause != null && cause.javaClass == javaClass) {
+ val currentTrace = stackTrace
+ if (currentTrace.any { it.isArtificial() })
+ cause as E to currentTrace
+ else this to emptyArray()
+ } else {
+ this to emptyArray()
+ }
+}
+
+private fun mergeRecoveredTraces(recoveredStacktrace: Array<StackTraceElement>, result: ArrayDeque<StackTraceElement>) {
+ // Merge two stacktraces and trim common prefix
+ val startIndex = recoveredStacktrace.indexOfFirst { it.isArtificial() } + 1
+ val lastFrameIndex = recoveredStacktrace.size - 1
+ for (i in lastFrameIndex downTo startIndex) {
+ val element = recoveredStacktrace[i]
+ if (element.elementWiseEquals(result.last)) {
+ result.removeLast()
+ }
+ result.addFirst(recoveredStacktrace[i])
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing {
+ if (!RECOVER_STACK_TRACES) throw exception
+ suspendCoroutineUninterceptedOrReturn<Nothing> {
+ if (it !is CoroutineStackFrame) throw exception
+ throw recoverFromStackFrame(exception, it)
+ }
+}
+
+internal actual fun <E : Throwable> unwrap(exception: E): E {
+ if (!RECOVER_STACK_TRACES) return exception
+ val cause = exception.cause
+ // Fast-path to avoid array cloning
+ if (cause == null || cause.javaClass != exception.javaClass) {
+ return exception
+ }
+ // Slow path looks for artificial frames in a stack-trace
+ if (exception.stackTrace.any { it.isArtificial() }) {
+ @Suppress("UNCHECKED_CAST")
+ return cause as E
+ } else {
+ return exception
+ }
+}
+
+private fun createStackTrace(continuation: CoroutineStackFrame): ArrayDeque<StackTraceElement> {
+ val stack = ArrayDeque<StackTraceElement>()
+ continuation.getStackTraceElement()?.let { stack.add(it) }
+
+ var last = continuation
+ while (true) {
+ last = (last as? CoroutineStackFrame)?.callerFrame ?: break
+ last.getStackTraceElement()?.let { stack.add(it) }
+ }
+ return stack
+}
+
+/**
+ * @suppress
+ */
+@InternalCoroutinesApi
+public fun artificialFrame(message: String) = java.lang.StackTraceElement("\b\b\b($message", "\b", "\b", -1)
+internal fun StackTraceElement.isArtificial() = className.startsWith("\b\b\b")
+private fun Array<StackTraceElement>.frameIndex(methodName: String) = indexOfFirst { methodName == it.className }
+
+private fun StackTraceElement.elementWiseEquals(e: StackTraceElement): Boolean {
+ /*
+ * In order to work on Java 9 where modules and classloaders of enclosing class
+ * are part of the comparison
+ */
+ return lineNumber == e.lineNumber && methodName == e.methodName
+ && fileName == e.fileName && className == e.className
+}
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias CoroutineStackFrame = kotlin.coroutines.jvm.internal.CoroutineStackFrame
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias StackTraceElement = java.lang.StackTraceElement
diff --git a/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
new file mode 100644
index 00000000..51dcee4b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual typealias SynchronizedObject = Any
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
+ kotlin.synchronized(lock, block) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt b/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt
new file mode 100644
index 00000000..ef5ab241
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmName("SystemPropsKt")
+@file:JvmMultifileClass
+
+package kotlinx.coroutines.internal
+
+// number of processors at startup for consistent prop initialization
+internal val AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors()
+
+internal actual fun systemProp(
+ propertyName: String
+): String? =
+ try {
+ System.getProperty(propertyName)
+ } catch (e: SecurityException) {
+ null
+ }
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt
new file mode 100644
index 00000000..375dc60b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+
+
+private val ZERO = Symbol("ZERO")
+
+// Used when there are >= 2 active elements in the context
+private class ThreadState(val context: CoroutineContext, n: Int) {
+ private var a = arrayOfNulls<Any>(n)
+ private var i = 0
+
+ fun append(value: Any?) { a[i++] = value }
+ fun take() = a[i++]
+ fun start() { i = 0 }
+}
+
+// Counts ThreadContextElements in the context
+// Any? here is Int | ThreadContextElement (when count is one)
+private val countAll =
+ fun (countOrElement: Any?, element: CoroutineContext.Element): Any? {
+ if (element is ThreadContextElement<*>) {
+ val inCount = countOrElement as? Int ?: 1
+ return if (inCount == 0) element else inCount + 1
+ }
+ return countOrElement
+ }
+
+// Find one (first) ThreadContextElement in the context, it is used when we know there is exactly one
+private val findOne =
+ fun (found: ThreadContextElement<*>?, element: CoroutineContext.Element): ThreadContextElement<*>? {
+ if (found != null) return found
+ return element as? ThreadContextElement<*>
+ }
+
+// Updates state for ThreadContextElements in the context using the given ThreadState
+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())
+ }
+ return state
+ }
+
+internal actual fun threadContextElements(context: CoroutineContext): Any = context.fold(0, countAll)!!
+
+// countOrElement is pre-cached in dispatched continuation
+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
+ // ^^^ 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
+ context.fold(ThreadState(context, countOrElement), updateState)
+ }
+ else -> {
+ // fast path for one ThreadContextElement (no allocations, no additional context scan)
+ @Suppress("UNCHECKED_CAST")
+ val element = countOrElement as ThreadContextElement<Any?>
+ element.updateThreadContext(context)
+ }
+ }
+}
+
+internal fun restoreThreadContext(context: CoroutineContext, oldState: Any?) {
+ when {
+ oldState === ZERO -> return // very fast path when there are no ThreadContextElements
+ oldState is ThreadState -> {
+ // slow path with multiple stored ThreadContextElements
+ oldState.start()
+ context.fold(oldState, restoreState)
+ }
+ else -> {
+ // fast path for one ThreadContextElement, but need to find it
+ @Suppress("UNCHECKED_CAST")
+ val element = context.fold(null, findOne) as ThreadContextElement<Any?>
+ element.restoreThreadContext(context, oldState)
+ }
+ }
+}
+
+// top-level data class for a nicer out-of-the-box toString representation and class name
+@PublishedApi
+internal data class ThreadLocalKey(private val threadLocal: ThreadLocal<*>) : CoroutineContext.Key<ThreadLocalElement<*>>
+
+internal class ThreadLocalElement<T>(
+ private val value: T,
+ private val threadLocal: ThreadLocal<T>
+) : ThreadContextElement<T> {
+ override val key: CoroutineContext.Key<*> = ThreadLocalKey(threadLocal)
+
+ override fun updateThreadContext(context: CoroutineContext): T {
+ val oldState = threadLocal.get()
+ threadLocal.set(value)
+ return oldState
+ }
+
+ override fun restoreThreadContext(context: CoroutineContext, oldState: T) {
+ threadLocal.set(oldState)
+ }
+
+ // this method is overridden to perform value comparison (==) on key
+ override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext {
+ return if (this.key == key) EmptyCoroutineContext else this
+ }
+
+ // this method is overridden to perform value comparison (==) on key
+ public override operator fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? =
+ @Suppress("UNCHECKED_CAST")
+ if (this.key == key) this as E else null
+
+ override fun toString(): String = "ThreadLocal(value=$value, threadLocal = $threadLocal)"
+}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt
new file mode 100644
index 00000000..39c87bed
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import java.lang.ThreadLocal
+
+@Suppress("ACTUAL_WITHOUT_EXPECT") // internal visibility
+internal actual typealias CommonThreadLocal<T> = ThreadLocal<T>
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
new file mode 100644
index 00000000..4089710e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
@@ -0,0 +1,1019 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import java.io.*
+import java.util.*
+import java.util.concurrent.*
+import java.util.concurrent.locks.*
+
+/**
+ * Coroutine scheduler (pool of shared threads) which primary target is to distribute dispatched coroutines over worker threads,
+ * including both CPU-intensive and blocking tasks.
+ *
+ * Current scheduler implementation has two optimization targets:
+ * * Efficiency in the face of communication patterns (e.g., actors communicating via channel)
+ * * Dynamic resizing to support blocking calls without re-dispatching coroutine to separate "blocking" thread pool
+ *
+ * ### Structural overview
+ *
+ * Scheduler consists of [corePoolSize] worker threads to execute CPU-bound tasks and up to [maxPoolSize] (lazily created) threads
+ * to execute blocking tasks. Every worker has local queue in addition to global scheduler queue and global queue
+ * has priority over local queue to avoid starvation of externally-submitted (e.g., from Android UI thread) tasks and work-stealing is implemented
+ * on top of that queues to provide even load distribution and illusion of centralized run queue.
+ *
+ * ### Scheduling
+ *
+ * When a coroutine is dispatched from within scheduler worker, it's placed into the head of worker run queue.
+ * If the head is not empty, the task from the head is moved to the tail. Though it is unfair scheduling policy,
+ * it effectively couples communicating coroutines into one and eliminates scheduling latency that arises from placing task to the end of the queue.
+ * Placing former head to the tail is necessary to provide semi-FIFO order, otherwise queue degenerates to stack.
+ * When a coroutine is dispatched from an external thread, it's put into the global queue.
+ *
+ * ### Work stealing and affinity
+ *
+ * To provide even tasks distribution worker tries to steal tasks from other workers queues before parking when his local queue is empty.
+ * A non-standard solution is implemented to provide tasks affinity: task may be stolen only if it's 'stale' enough (based on the value of [WORK_STEALING_TIME_RESOLUTION_NS]).
+ * For this purpose monotonic global clock ([System.nanoTime]) is used and every task has associated with it submission time.
+ * This approach shows outstanding results when coroutines are cooperative, but as downside scheduler now depends on high-resolution global clock
+ * which may limit scalability on NUMA machines.
+ *
+ * ### Dynamic resizing and support of blocking tasks
+ *
+ * To support possibly blocking tasks [TaskMode] and CPU quota (via [cpuPermits]) are used.
+ * To execute [TaskMode.NON_BLOCKING] tasks from the global queue or to steal tasks from other workers
+ * the worker should have CPU permit. When a worker starts executing [TaskMode.PROBABLY_BLOCKING] task,
+ * it releases its CPU permit, giving a hint to a scheduler that additional thread should be created (or awaken)
+ * if new [TaskMode.NON_BLOCKING] task will arrive. When a worker finishes executing blocking task, it executes
+ * all tasks from its local queue (including [TaskMode.NON_BLOCKING]) and then parks as retired without polling
+ * global queue or trying to steal new tasks. Such approach may slightly limit scalability (allowing more than [corePoolSize] threads
+ * to execute CPU-bound tasks at once), but in practice, it is not, significantly reducing context switches and tasks re-dispatching.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+@Suppress("NOTHING_TO_INLINE")
+internal class CoroutineScheduler(
+ private val corePoolSize: Int,
+ private val maxPoolSize: Int,
+ private val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
+ private val schedulerName: String = DEFAULT_SCHEDULER_NAME
+) : Executor, Closeable {
+ init {
+ require(corePoolSize >= MIN_SUPPORTED_POOL_SIZE) {
+ "Core pool size $corePoolSize should be at least $MIN_SUPPORTED_POOL_SIZE"
+ }
+ require(maxPoolSize >= corePoolSize) {
+ "Max pool size $maxPoolSize should be greater than or equals to core pool size $corePoolSize"
+ }
+ require(maxPoolSize <= MAX_SUPPORTED_POOL_SIZE) {
+ "Max pool size $maxPoolSize should not exceed maximal supported number of threads $MAX_SUPPORTED_POOL_SIZE"
+ }
+ require(idleWorkerKeepAliveNs > 0) {
+ "Idle worker keep alive time $idleWorkerKeepAliveNs must be positive"
+ }
+ }
+
+ private val globalQueue: GlobalQueue = GlobalQueue()
+
+ /**
+ * Permits to execute non-blocking (~CPU-intensive) tasks.
+ * If worker owns a permit, it can schedule non-blocking tasks to its queue and steal work from other workers.
+ * If worker doesn't, it can execute only blocking tasks (and non-blocking leftovers from its local queue)
+ * and will try to park as soon as its queue is empty.
+ */
+ private val cpuPermits = Semaphore(corePoolSize, false)
+
+ /**
+ * The stack of parker workers.
+ * Every worker registers itself in a stack before parking (if it was not previously registered)
+ * and callers of [requestCpuWorker] will try to unpark a thread from the top of a stack.
+ * This is a form of intrusive garbage-free Treiber stack where Worker also is a stack node.
+ *
+ * The stack is better than a queue (even with contention on top) because it unparks threads
+ * in most-recently used order, improving both performance and locality.
+ * Moreover, it decreases threads thrashing, if the pool has n threads when only n / 2 is required,
+ * the latter half will never be unparked and will terminate itself after [IDLE_WORKER_KEEP_ALIVE_NS].
+ *
+ * This long version consist of version bits with [PARKED_VERSION_MASK]
+ * and top worker thread index bits with [PARKED_INDEX_MASK].
+ */
+ private val parkedWorkersStack = atomic(0L)
+
+ /**
+ * Updates index of the worker at the top of [parkedWorkersStack].
+ * It always updates version to ensure interference with [parkedWorkersStackPop] operation
+ * that might have already decided to put this index to the top.
+ *
+ * Note, [newIndex] can be zero for the worker that is being terminated (removed from [workers]).
+ */
+ private 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
+ val updIndex = if (index == oldIndex) {
+ if (newIndex == 0) {
+ parkedWorkersStackNextIndex(worker)
+ } else {
+ newIndex
+ }
+ } else {
+ index // no change to index, but update version
+ }
+ if (updIndex < 0) return@loop // retry
+ if (parkedWorkersStack.compareAndSet(top, updVersion or updIndex.toLong())) return
+ }
+ }
+
+ /**
+ * Pushes worker into [parkedWorkersStack].
+ * 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].
+ */
+ private fun parkedWorkersStackPush(worker: Worker) {
+ if (worker.nextParkedWorker !== NOT_IN_STACK) return // 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
+ * can add it to the stack (only the worker itself), this invariant holds while this loop executes.
+ */
+ parkedWorkersStack.loop { top ->
+ val index = (top and PARKED_INDEX_MASK).toInt()
+ val updVersion = (top + PARKED_VERSION_INC) and PARKED_VERSION_MASK
+ val updIndex = worker.indexInArray
+ assert { updIndex != 0 } // only this worker can push itself, cannot be terminated
+ worker.nextParkedWorker = workers[index]
+ /*
+ * Other thread can be changing this worker's index at this point, but it
+ * also invokes parkedWorkersStackTopUpdate which updates version to make next CAS fail.
+ * Successful CAS of the stack top completes successful push.
+ */
+ if (parkedWorkersStack.compareAndSet(top, updVersion or updIndex.toLong())) return
+ }
+ }
+
+ /**
+ * Pops worker from [parkedWorkersStack].
+ * It can be invoked concurrently from any thread that is looking for help and needs to unpark some worker.
+ * This invocation is always followed by an attempt to [LockSupport.unpark] resulting worker.
+ * See [tryUnpark].
+ */
+ private fun parkedWorkersStackPop(): Worker? {
+ parkedWorkersStack.loop { top ->
+ val index = (top and PARKED_INDEX_MASK).toInt()
+ val worker = workers[index] ?: return null // stack is empty
+ val updVersion = (top + PARKED_VERSION_INC) and PARKED_VERSION_MASK
+ val updIndex = parkedWorkersStackNextIndex(worker)
+ if (updIndex < 0) return@loop // retry
+ /*
+ * Other thread can be changing this worker's index at this point, but it
+ * also invokes parkedWorkersStackTopUpdate which updates version to make next CAS fail.
+ * Successful CAS of the stack top completes successful pop.
+ */
+ if (parkedWorkersStack.compareAndSet(top, updVersion or updIndex.toLong())) {
+ /*
+ * We've just took worker out of the stack, but nextParkerWorker is not reset yet, so if a worker is
+ * currently invoking parkedWorkersStackPush it would think it is in the stack and bail out without
+ * adding itself again. It does not matter, since we are going it invoke unpark on the thread
+ * that was popped out of parkedWorkersStack anyway.
+ */
+ worker.nextParkedWorker = NOT_IN_STACK
+ return worker
+ }
+ }
+ }
+
+ /**
+ * Finds next usable index for [parkedWorkersStack]. The problem is that workers can
+ * be terminated at their [Worker.indexInArray] becomes zero, so they cannot be
+ * put at the top of the stack. In which case we are looking for next.
+ *
+ * Returns `index >= 0` or `-1` for retry.
+ */
+ private fun parkedWorkersStackNextIndex(worker: Worker): Int {
+ var next = worker.nextParkedWorker
+ findNext@ while (true) {
+ when {
+ next === NOT_IN_STACK -> return -1 // we are too late -- other thread popped this element, retry
+ next === null -> return 0 // stack becomes empty
+ else -> {
+ val nextWorker = next as Worker
+ val updIndex = nextWorker.indexInArray
+ if (updIndex != 0) return updIndex // found good index for next worker
+ // Otherwise, this worker was terminated and we cannot put it to top anymore, check next
+ next = nextWorker.nextParkedWorker
+ }
+ }
+ }
+ }
+
+ /**
+ * State of worker threads.
+ * [workers] is array of lazily created workers up to [maxPoolSize] workers.
+ * [createdWorkers] is count of already created workers (worker with index lesser than [createdWorkers] exists).
+ * [blockingWorkers] is count of running workers which are executing [TaskMode.PROBABLY_BLOCKING] task.
+ * All mutations of array's content are guarded by lock.
+ *
+ * **NOTE**: `workers[0]` is always `null` (never used, works as sentinel value), so
+ * workers are 1-indexed, code path in [Worker.trySteal] is a bit faster and index swap during termination
+ * works properly
+ */
+ private val workers: Array<Worker?> = arrayOfNulls(maxPoolSize + 1)
+
+ /**
+ * Long describing state of workers in this pool.
+ * Currently includes created and blocking workers each occupying [BLOCKING_SHIFT] bits.
+ */
+ private val controlState = atomic(0L)
+
+ private val createdWorkers: Int inline get() = (controlState.value and CREATED_MASK).toInt()
+ private val blockingWorkers: Int inline get() = (controlState.value and BLOCKING_MASK shr BLOCKING_SHIFT).toInt()
+
+ private inline fun createdWorkers(state: Long): Int = (state and CREATED_MASK).toInt()
+ private inline fun blockingWorkers(state: Long): Int = (state and BLOCKING_MASK shr BLOCKING_SHIFT).toInt()
+
+ // Guarded by synchronization
+ private inline fun incrementCreatedWorkers(): Int = createdWorkers(controlState.incrementAndGet())
+ private inline fun decrementCreatedWorkers(): Int = createdWorkers(controlState.getAndDecrement())
+
+ private inline fun incrementBlockingWorkers() { controlState.addAndGet(1L shl BLOCKING_SHIFT) }
+ private inline fun decrementBlockingWorkers() { controlState.addAndGet(-(1L shl BLOCKING_SHIFT)) }
+
+ private val random = Random()
+
+ // This is used a "stop signal" for close and shutdown functions
+ private val _isTerminated = atomic(0) // todo: replace with atomic boolean on new versions of atomicFu
+ private val isTerminated: Boolean get() = _isTerminated.value != 0
+
+ companion object {
+ private val MAX_SPINS = systemProp("kotlinx.coroutines.scheduler.spins", 1000, minValue = 1)
+ private val MAX_YIELDS = MAX_SPINS + systemProp("kotlinx.coroutines.scheduler.yields", 0, minValue = 0)
+
+ @JvmStatic // Note that is fits into Int (it is equal to 10^9)
+ private val MAX_PARK_TIME_NS = TimeUnit.SECONDS.toNanos(1).toInt()
+
+ @JvmStatic
+ private val MIN_PARK_TIME_NS = (WORK_STEALING_TIME_RESOLUTION_NS / 4)
+ .coerceAtLeast(10)
+ .coerceAtMost(MAX_PARK_TIME_NS.toLong()).toInt()
+
+ // A symbol to mark workers that are not in parkedWorkersStack
+ private val NOT_IN_STACK = Symbol("NOT_IN_STACK")
+
+ // Local queue 'add' results
+ private const val ADDED = -1
+ // Added to the local queue, but pool requires additional worker to keep up
+ private const val ADDED_REQUIRES_HELP = 0
+ private const val NOT_ADDED = 1
+
+ // Worker termination states
+ private const val FORBIDDEN = -1
+ private const val ALLOWED = 0
+ private const val TERMINATED = 1
+
+ // Masks of control state
+ private const val BLOCKING_SHIFT = 21 // 2M threads max
+ private const val CREATED_MASK: Long = (1L shl BLOCKING_SHIFT) - 1
+ private const val BLOCKING_MASK: Long = CREATED_MASK shl BLOCKING_SHIFT
+
+ internal const val MIN_SUPPORTED_POOL_SIZE = 1 // we support 1 for test purposes, but it is not usually used
+ internal const val MAX_SUPPORTED_POOL_SIZE = (1 shl BLOCKING_SHIFT) - 2
+
+ // Masks of parkedWorkersStack
+ private const val PARKED_INDEX_MASK = CREATED_MASK
+ private const val PARKED_VERSION_MASK = CREATED_MASK.inv()
+ private const val PARKED_VERSION_INC = 1L shl BLOCKING_SHIFT
+ }
+
+ override fun execute(command: Runnable) = dispatch(command)
+
+ override fun close() = shutdown(10_000L)
+
+ // Shuts down current scheduler and waits until all work is done and all threads are stopped.
+ fun shutdown(timeout: Long) {
+ // atomically set termination flag which is checked when workers are added or removed
+ if (!_isTerminated.compareAndSet(0, 1)) return
+ // make sure we are not waiting for the current thread
+ val currentWorker = currentWorker()
+ // Capture # of created workers that cannot change anymore (mind the synchronized block!)
+ val created = synchronized(workers) { createdWorkers }
+ // Shutdown all workers with the only exception of the current thread
+ for (i in 1..created) {
+ val worker = workers[i]!!
+ if (worker !== currentWorker) {
+ while (worker.isAlive) {
+ LockSupport.unpark(worker)
+ worker.join(timeout)
+ }
+ val state = worker.state
+ assert { state === WorkerState.TERMINATED } // Expected TERMINATED state
+ worker.localQueue.offloadAllWork(globalQueue)
+ }
+ }
+ // Make sure no more work is added to GlobalQueue from anywhere
+ globalQueue.close()
+ // Finish processing tasks from globalQueue and/or from this worker's local queue
+ while (true) {
+ val task = currentWorker?.findTask() ?: globalQueue.removeFirstOrNull() ?: break
+ runSafely(task)
+ }
+ // Shutdown current thread
+ currentWorker?.tryReleaseCpu(WorkerState.TERMINATED)
+ // check & cleanup state
+ assert { cpuPermits.availablePermits() == corePoolSize }
+ parkedWorkersStack.value = 0L
+ controlState.value = 0L
+ }
+
+ /**
+ * Dispatches execution of a runnable [block] with a hint to a scheduler whether
+ * this [block] may execute blocking operations (IO, system calls, locking primitives etc.)
+ *
+ * @param block runnable to be dispatched
+ * @param taskContext concurrency context of given [block]
+ * @param fair whether the task should be dispatched fairly (strict FIFO) or not (semi-FIFO)
+ */
+ fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {
+ trackTask() // this is needed for virtual time support
+ val task = createTask(block, taskContext)
+ // try to submit the task to the local queue and act depending on the result
+ when (submitToLocalQueue(task, fair)) {
+ ADDED -> return
+ NOT_ADDED -> {
+ // try to offload task to global queue
+ if (!globalQueue.addLast(task)) {
+ // Global queue is closed in the last step of close/shutdown -- no more tasks should be accepted
+ throw RejectedExecutionException("$schedulerName was terminated")
+ }
+ requestCpuWorker()
+ }
+ else -> requestCpuWorker() // ask for help
+ }
+ }
+
+ internal fun createTask(block: Runnable, taskContext: TaskContext): Task {
+ val nanoTime = schedulerTimeSource.nanoTime()
+ if (block is Task) {
+ block.submissionTime = nanoTime
+ block.taskContext = taskContext
+ return block
+ }
+ return TaskImpl(block, nanoTime, taskContext)
+ }
+
+ /**
+ * Unparks or creates a [Worker] for executing non-blocking tasks if there are idle cores
+ */
+ private fun requestCpuWorker() {
+ // No CPU available -- nothing to request
+ if (cpuPermits.availablePermits() == 0) {
+ tryUnpark()
+ return
+ }
+ /*
+ * Fast path -- we have retired or parked worker, unpark it and we're done.
+ * The data race here: when only one permit is available, multiple retired workers
+ * can be unparked, but only one will continue execution, so we're overproviding with threads
+ * in case of race to avoid spurious starvation
+ */
+ if (tryUnpark()) return
+ /*
+ * Create a thread.
+ * It's not preferable to use 'cpuWorkersCounter' here (moreover, it's implicitly here as corePoolSize - cpuPermits.availableTokens),
+ * cpuWorkersCounter doesn't take into account threads which are created (and either running or parked), but haven't
+ * CPU token: retiring workers, recently unparked workers before `findTask` call, etc.
+ * So if we will use cpuWorkersCounter, we start to overprovide with threads too much.
+ */
+ val state = controlState.value
+ val created = createdWorkers(state)
+ val blocking = blockingWorkers(state)
+ val cpuWorkers = created - blocking
+ /*
+ * We check how many threads are there to handle non-blocking work,
+ * and create one more if we have not enough of them.
+ */
+ if (cpuWorkers < corePoolSize) {
+ val newCpuWorkers = createNewWorker()
+ // If we've created the first cpu worker and corePoolSize > 1 then create
+ // one more (second) cpu worker, so that stealing between them is operational
+ if (newCpuWorkers == 1 && corePoolSize > 1) createNewWorker()
+ if (newCpuWorkers > 0) return
+ }
+ // Try unpark again in case there was race between permit release and parking
+ tryUnpark()
+ }
+
+ private fun tryUnpark(): Boolean {
+ while (true) {
+ val worker = parkedWorkersStackPop() ?: return false
+ /*
+ * If we successfully took the worker out of the queue, it could be in the following states:
+ * 1) Worker is parked, then we'd like to reset its spin and park counters, so after
+ * unpark it will try to steal from every worker at least once
+ * 2) Worker is not parked, but it actually idle and
+ * tries to find work. Then idle reset is required as well.
+ * Worker state may be either PARKING or CPU_ACQUIRED (from `findTask`)
+ * 3) Worker is active (unparked itself from `idleCpuWorker`), found tasks to do and is currently busy.
+ * Then `idleResetBeforeUnpark` will do nothing, but we can't distinguish this state from previous
+ * one, so just retry.
+ * 4) Worker is terminated. No harm in resetting its counters either.
+ */
+ worker.idleResetBeforeUnpark()
+ /*
+ * Check that the thread we've found in the queue was indeed in parking state, before we
+ * actually try to unpark it.
+ */
+ val wasParking = worker.isParking
+ /*
+ * Send unpark signal anyway, because the thread may have made decision to park but have not yet set its
+ * state to parking and this could be the last thread we have (unparking random thread would not harm).
+ */
+ LockSupport.unpark(worker)
+ /*
+ * If this thread was not in parking state then we definitely need to find another thread.
+ * We err on the side of unparking more threads than needed here.
+ */
+ if (!wasParking) continue
+ /*
+ * Terminating worker could be selected.
+ * If it's already TERMINATED or we cannot forbid it from terminating, then try find another worker.
+ */
+ if (!worker.tryForbidTermination()) continue
+ /*
+ * Here we've successfully unparked a thread that was parked and had forbidden it from making
+ * decision to terminate, so we are now sure we've got some help.
+ */
+ return true
+ }
+ }
+
+ /*
+ * Returns the number of CPU workers after this function (including new worker) or
+ * 0 if no worker was created.
+ */
+ private fun createNewWorker(): Int {
+ synchronized(workers) {
+ // Make sure we're not trying to resurrect terminated scheduler
+ if (isTerminated) return -1
+ val state = controlState.value
+ val created = createdWorkers(state)
+ val blocking = blockingWorkers(state)
+ val cpuWorkers = created - blocking
+ // Double check for overprovision
+ if (cpuWorkers >= corePoolSize) return 0
+ if (created >= maxPoolSize || cpuPermits.availablePermits() == 0) return 0
+ // start & register new worker, commit index only after successful creation
+ val newIndex = createdWorkers + 1
+ require(newIndex > 0 && workers[newIndex] == null)
+ val worker = Worker(newIndex).apply { start() }
+ require(newIndex == incrementCreatedWorkers())
+ workers[newIndex] = worker
+ return cpuWorkers + 1
+ }
+ }
+
+ /**
+ * Returns [ADDED], or [NOT_ADDED], or [ADDED_REQUIRES_HELP].
+ */
+ private fun submitToLocalQueue(task: Task, fair: Boolean): Int {
+ val worker = currentWorker() ?: return NOT_ADDED
+
+ /*
+ * This worker could have been already terminated from this thread by close/shutdown and it should not
+ * accept any more tasks into its local queue.
+ */
+ if (worker.state === WorkerState.TERMINATED) return NOT_ADDED
+
+ var result = ADDED
+ if (task.mode == TaskMode.NON_BLOCKING) {
+ /*
+ * If the worker is currently executing blocking task and tries to dispatch non-blocking task, it's one the following reasons:
+ * 1) Blocking worker is finishing its block and resumes non-blocking continuation
+ * 2) Blocking worker starts to create non-blocking jobs
+ *
+ * First use-case is expected (as recommended way of using blocking contexts),
+ * so we add non-blocking task to local queue, but also request CPU worker to mitigate second case
+ */
+ if (worker.isBlocking) {
+ result = ADDED_REQUIRES_HELP
+ } else {
+ /*
+ * If thread is not blocking, then it's just tries to finish its
+ * local work in order to park (or grab another blocking task), do not add non-blocking tasks
+ * to its local queue if it can't acquire CPU
+ */
+ val hasPermit = worker.tryAcquireCpuPermit()
+ if (!hasPermit) {
+ return NOT_ADDED
+ }
+ }
+ }
+
+ val noOffloadingHappened = if (fair) {
+ worker.localQueue.addLast(task, globalQueue)
+ } else {
+ worker.localQueue.add(task, globalQueue)
+ }
+
+ if (noOffloadingHappened) {
+ // When we're close to queue capacity, wake up anyone to steal work
+ // Note: non-atomic bufferSize here is Ok (it is just a performance optimization)
+ if (worker.localQueue.bufferSize > QUEUE_SIZE_OFFLOAD_THRESHOLD) {
+ return ADDED_REQUIRES_HELP
+ }
+ return result
+ }
+ return ADDED_REQUIRES_HELP
+ }
+
+ private fun currentWorker(): Worker? = (Thread.currentThread() as? Worker)?.takeIf { it.scheduler == this }
+
+ /**
+ * Returns a string identifying the state of this scheduler for nicer debugging.
+ * Note that this method is not atomic and represents rough state of pool.
+ *
+ * State of the queues:
+ * b for blocking, c for CPU, r for retiring.
+ * E.g. for [1b, 1b, 2c, 1r] means that pool has
+ * two blocking workers with queue size 1, one worker with CPU permit and queue size 1
+ * and one retiring (executing his local queue before parking) worker with queue size 1.
+ */
+ override fun toString(): String {
+ var parkedWorkers = 0
+ var blockingWorkers = 0
+ var cpuWorkers = 0
+ var retired = 0
+ var terminated = 0
+ val queueSizes = arrayListOf<String>()
+ for (worker in workers) {
+ if (worker == null) continue
+ val queueSize = worker.localQueue.size()
+ when (worker.state) {
+ WorkerState.PARKING -> ++parkedWorkers
+ WorkerState.BLOCKING -> {
+ ++blockingWorkers
+ queueSizes += queueSize.toString() + "b" // Blocking
+ }
+ WorkerState.CPU_ACQUIRED -> {
+ ++cpuWorkers
+ queueSizes += queueSize.toString() + "c" // CPU
+ }
+ WorkerState.RETIRING -> {
+ ++retired
+ if (queueSize > 0) queueSizes += queueSize.toString() + "r" // Retiring
+ }
+ WorkerState.TERMINATED -> ++terminated
+ }
+ }
+ val state = controlState.value
+ return "$schedulerName@$hexAddress[" +
+ "Pool Size {" +
+ "core = $corePoolSize, " +
+ "max = $maxPoolSize}, " +
+ "Worker States {" +
+ "CPU = $cpuWorkers, " +
+ "blocking = $blockingWorkers, " +
+ "parked = $parkedWorkers, " +
+ "retired = $retired, " +
+ "terminated = $terminated}, " +
+ "running workers queues = $queueSizes, "+
+ "global queue size = ${globalQueue.size}, " +
+ "Control State Workers {" +
+ "created = ${createdWorkers(state)}, " +
+ "blocking = ${blockingWorkers(state)}}" +
+ "]"
+ }
+
+ private fun runSafely(task: Task) {
+ try {
+ task.run()
+ } catch (e: Throwable) {
+ val thread = Thread.currentThread()
+ thread.uncaughtExceptionHandler.uncaughtException(thread, e)
+ } finally {
+ unTrackTask()
+ }
+ }
+
+ internal inner class Worker private constructor() : Thread() {
+ init {
+ isDaemon = true
+ }
+
+ // guarded by scheduler lock, index in workers array, 0 when not in array (terminated)
+ @Volatile // volatile for push/pop operation into parkedWorkersStack
+ var indexInArray = 0
+ set(index) {
+ name = "$schedulerName-worker-${if (index == 0) "TERMINATED" else index.toString()}"
+ field = index
+ }
+
+ constructor(index: Int) : this() {
+ indexInArray = index
+ }
+
+ val scheduler get() = this@CoroutineScheduler
+
+ val localQueue: WorkQueue = WorkQueue()
+
+ /**
+ * Worker state. **Updated only by this worker thread**.
+ * By default, worker is in RETIRING state in the case when it was created, but all CPU tokens or tasks were taken.
+ */
+ @Volatile
+ var state = WorkerState.RETIRING
+
+ val isParking: Boolean get() = state == WorkerState.PARKING
+ val isBlocking: Boolean get() = state == WorkerState.BLOCKING
+
+ /**
+ * Small state machine for termination.
+ * Followed states are allowed:
+ * [ALLOWED] -- worker can wake up and terminate itself
+ * [FORBIDDEN] -- worker is not allowed to terminate (because it was chosen by another thread to help)
+ * [TERMINATED] -- final state, thread is terminating and cannot be resurrected
+ *
+ * Allowed transitions:
+ * [ALLOWED] -> [FORBIDDEN]
+ * [ALLOWED] -> [TERMINATED]
+ * [FORBIDDEN] -> [ALLOWED]
+ */
+ private val terminationState = atomic(ALLOWED)
+
+ /**
+ * It is set to the termination deadline when started doing [blockingWorkerIdle] and it reset
+ * when there is a task. It servers as protection against spurious wakeups of parkNanos.
+ */
+ private var terminationDeadline = 0L
+
+ /**
+ * Reference to the next worker in the [parkedWorkersStack].
+ * It may be `null` if there is no next parked worker.
+ * This reference is set to [NOT_IN_STACK] when worker is physically not in stack.
+ */
+ @Volatile
+ var nextParkedWorker: Any? = NOT_IN_STACK
+
+ /**
+ * Tries to set [terminationState] to [FORBIDDEN], returns `false` if this attempt fails.
+ * This attempt may fail either because worker terminated itself or because someone else
+ * claimed this worker (though this case is rare, because require very bad timings)
+ */
+ fun tryForbidTermination(): Boolean =
+ when (val state = terminationState.value) {
+ TERMINATED -> false // already terminated
+ FORBIDDEN -> false // already forbidden, someone else claimed this worker
+ ALLOWED -> terminationState.compareAndSet(
+ ALLOWED,
+ FORBIDDEN
+ )
+ else -> error("Invalid terminationState = $state")
+ }
+
+ /**
+ * Tries to acquire CPU token if worker doesn't have one
+ * @return whether worker has CPU token
+ */
+ fun tryAcquireCpuPermit(): Boolean {
+ return when {
+ state == WorkerState.CPU_ACQUIRED -> true
+ cpuPermits.tryAcquire() -> {
+ state = WorkerState.CPU_ACQUIRED
+ true
+ }
+ else -> false
+ }
+ }
+
+ /**
+ * Releases CPU token if worker has any and changes state to [newState]
+ * @return whether worker had CPU token
+ */
+ internal fun tryReleaseCpu(newState: WorkerState): Boolean {
+ val previousState = state
+ val hadCpu = previousState == WorkerState.CPU_ACQUIRED
+ if (hadCpu) cpuPermits.release()
+ if (previousState != newState) state = newState
+ return hadCpu
+ }
+
+ /**
+ * Time of the last call to [requestCpuWorker] due to missing tasks deadlines.
+ * Used as throttling mechanism to avoid unparking multiple threads when it's not necessary
+ */
+ private var lastExhaustionTime = 0L
+
+ @Volatile // Required for concurrent idleResetBeforeUnpark
+ private var spins = 0 // spins until MAX_SPINS, then yields until MAX_YIELDS
+
+ // Note: it is concurrently reset by idleResetBeforeUnpark
+ private var parkTimeNs = MIN_PARK_TIME_NS
+
+ private var rngState = random.nextInt()
+ private var lastStealIndex = 0 // try in order repeated, reset when unparked
+
+ override fun run() {
+ var wasIdle = false // local variable to avoid extra idleReset invocations when tasks repeatedly arrive
+ while (!isTerminated && state != WorkerState.TERMINATED) {
+ val task = findTask()
+ if (task == null) {
+ // Wait for a job with potential park
+ if (state == WorkerState.CPU_ACQUIRED) {
+ cpuWorkerIdle()
+ } else {
+ blockingWorkerIdle()
+ }
+ wasIdle = true
+ } else {
+ // Note: read task.mode before running the task, because Task object will be reused after run
+ val taskMode = task.mode
+ if (wasIdle) {
+ idleReset(taskMode)
+ wasIdle = false
+ }
+ beforeTask(taskMode, task.submissionTime)
+ runSafely(task)
+ afterTask(taskMode)
+ }
+ }
+ tryReleaseCpu(WorkerState.TERMINATED)
+ }
+
+ private fun beforeTask(taskMode: TaskMode, taskSubmissionTime: Long) {
+ if (taskMode != TaskMode.NON_BLOCKING) {
+ /*
+ * We should release CPU *before* checking for CPU starvation,
+ * otherwise requestCpuWorker() will not count current thread as blocking
+ */
+ incrementBlockingWorkers()
+ if (tryReleaseCpu(WorkerState.BLOCKING)) {
+ requestCpuWorker()
+ }
+ return
+ }
+ /*
+ * If we have idle CPU and the current worker is exhausted, wake up one more worker.
+ * Check last exhaustion time to avoid the race between steal and next task execution
+ */
+ if (cpuPermits.availablePermits() == 0) {
+ return
+ }
+ val now = schedulerTimeSource.nanoTime()
+ if (now - taskSubmissionTime >= WORK_STEALING_TIME_RESOLUTION_NS &&
+ now - lastExhaustionTime >= WORK_STEALING_TIME_RESOLUTION_NS * 5
+ ) {
+ lastExhaustionTime = now
+ requestCpuWorker()
+ }
+ }
+
+ private fun afterTask(taskMode: TaskMode) {
+ if (taskMode != TaskMode.NON_BLOCKING) {
+ decrementBlockingWorkers()
+ val currentState = state
+ // Shutdown sequence of blocking dispatcher
+ if (currentState !== WorkerState.TERMINATED) {
+ assert { currentState == WorkerState.BLOCKING } // "Expected BLOCKING state, but has $currentState"
+ state = WorkerState.RETIRING
+ }
+ }
+ }
+
+ /*
+ * 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 {
+ rngState = rngState xor (rngState shl 13)
+ rngState = rngState xor (rngState shr 17)
+ rngState = rngState xor (rngState shl 5)
+ val mask = upperBound - 1
+ // Fast path for power of two bound
+ if (mask and upperBound == 0) {
+ return rngState and mask
+ }
+ return (rngState and Int.MAX_VALUE) % upperBound
+ }
+
+ private fun cpuWorkerIdle() {
+ /*
+ * Simple adaptive await of work:
+ * Spin on the volatile field with an empty loop in hope that new work will arrive,
+ * then start yielding to reduce CPU pressure, and finally start adaptive parking.
+ *
+ * The main idea is not to park while it's possible (otherwise throughput on asymmetric workloads suffers due to too frequent
+ * park/unpark calls and delays between job submission and thread queue checking)
+ */
+ val spins = this.spins // volatile read
+ if (spins <= MAX_YIELDS) {
+ this.spins = spins + 1 // volatile write
+ if (spins >= MAX_SPINS) yield()
+ } else {
+ if (parkTimeNs < MAX_PARK_TIME_NS) {
+ parkTimeNs = (parkTimeNs * 3 ushr 1).coerceAtMost(MAX_PARK_TIME_NS)
+ }
+ tryReleaseCpu(WorkerState.PARKING)
+ doPark(parkTimeNs.toLong())
+ }
+ }
+
+ private fun blockingWorkerIdle() {
+ tryReleaseCpu(WorkerState.PARKING)
+ if (!blockingQuiescence()) return
+ terminationState.value = ALLOWED
+ // set termination deadline the first time we are here (it is reset in idleReset)
+ if (terminationDeadline == 0L) terminationDeadline = System.nanoTime() + idleWorkerKeepAliveNs
+ // actually park
+ if (!doPark(idleWorkerKeepAliveNs)) return
+ // try terminate when we are idle past termination deadline
+ // note that comparison is written like this to protect against potential nanoTime wraparound
+ if (System.nanoTime() - terminationDeadline >= 0) {
+ terminationDeadline = 0L // if attempt to terminate worker fails we'd extend deadline again
+ tryTerminateWorker()
+ }
+ }
+
+ private fun doPark(nanos: Long): Boolean {
+ /*
+ * Here we are trying to park, then check whether there are new blocking tasks
+ * (because submitting thread could have missed this thread in tryUnpark)
+ */
+ parkedWorkersStackPush(this)
+ if (!blockingQuiescence()) return false
+ LockSupport.parkNanos(nanos)
+ return true
+ }
+
+ /**
+ * Stops execution of current thread and removes it from [createdWorkers].
+ */
+ private fun tryTerminateWorker() {
+ synchronized(workers) {
+ // Make sure we're not trying race with termination of scheduler
+ if (isTerminated) return
+ // Someone else terminated, bail out
+ if (createdWorkers <= corePoolSize) return
+ // Try to find blocking task before termination
+ if (!blockingQuiescence()) return
+ /*
+ * See tryUnpark for state reasoning.
+ * If this CAS fails, then we were successfully unparked by other worker and cannot terminate.
+ */
+ if (!terminationState.compareAndSet(ALLOWED, TERMINATED)) return
+ /*
+ * At this point this thread is no longer considered as usable for scheduling.
+ * We need multi-step choreography to reindex workers.
+ *
+ * 1) Read current worker's index and reset it to zero.
+ */
+ val oldIndex = indexInArray
+ indexInArray = 0
+ /*
+ * Now this worker cannot become the top of parkedWorkersStack, but it can
+ * still be at the stack top via oldIndex.
+ *
+ * 2) Update top of stack if it was pointing to oldIndex and make sure no
+ * pending push/pop operation that might have already retrieved oldIndex could complete.
+ */
+ parkedWorkersStackTopUpdate(this, oldIndex, 0)
+ /*
+ * 3) Move last worker into an index in array that was previously occupied by this worker,
+ * if last worker was a different one (sic!).
+ */
+ val lastIndex = decrementCreatedWorkers()
+ if (lastIndex != oldIndex) {
+ val lastWorker = workers[lastIndex]!!
+ workers[oldIndex] = lastWorker
+ lastWorker.indexInArray = oldIndex
+ /*
+ * Now lastWorker is available at both indices in the array, but it can
+ * still be at the stack top on via its lastIndex
+ *
+ * 4) Update top of stack lastIndex -> oldIndex and make sure no
+ * pending push/pop operation that might have already retrieved lastIndex could complete.
+ */
+ parkedWorkersStackTopUpdate(lastWorker, lastIndex, oldIndex)
+ }
+ /*
+ * 5) It is safe to clear reference from workers array now.
+ */
+ workers[lastIndex] = null
+ }
+ state = WorkerState.TERMINATED
+ }
+
+ /**
+ * Checks whether new blocking tasks arrived to the pool when worker decided
+ * it can go to deep park/termination and puts recently arrived task to its local queue.
+ * Returns `true` if there is no blocking tasks in the queue.
+ */
+ private fun blockingQuiescence(): Boolean {
+ globalQueue.removeFirstWithModeOrNull(TaskMode.PROBABLY_BLOCKING)?.let {
+ localQueue.add(it, globalQueue)
+ return false
+ }
+ return true
+ }
+
+ // It is invoked by this worker when it finds a task
+ private fun idleReset(mode: TaskMode) {
+ terminationDeadline = 0L // reset deadline for termination
+ lastStealIndex = 0 // reset steal index (next time try random)
+ if (state == WorkerState.PARKING) {
+ assert { mode == TaskMode.PROBABLY_BLOCKING }
+ state = WorkerState.BLOCKING
+ parkTimeNs = MIN_PARK_TIME_NS
+ }
+ spins = 0
+ }
+
+ // It is invoked by other thread before this worker is unparked
+ fun idleResetBeforeUnpark() {
+ parkTimeNs = MIN_PARK_TIME_NS
+ spins = 0 // Volatile write, should be written last
+ }
+
+ internal fun findTask(): Task? {
+ if (tryAcquireCpuPermit()) return findTaskWithCpuPermit()
+ /*
+ * If the local queue is empty, try to extract blocking task from global queue.
+ * It's helpful for two reasons:
+ * 1) We won't call excess park/unpark here and someone's else CPU token won't be transferred,
+ * which is a performance win
+ * 2) It helps with rare race when external submitter sends depending blocking tasks
+ * one by one and one of the requested workers may miss CPU token
+ */
+ return localQueue.poll() ?: globalQueue.removeFirstWithModeOrNull(TaskMode.PROBABLY_BLOCKING)
+ }
+
+ private fun findTaskWithCpuPermit(): Task? {
+ /*
+ * Anti-starvation mechanism: if pool is overwhelmed by external work
+ * or local work is frequently offloaded, global queue polling will
+ * starve tasks from local queue. But if we never poll global queue,
+ * then local tasks may starve global queue, so poll global queue
+ * once per two core pool size iterations.
+ * Poll global queue only for non-blocking tasks as for blocking task a separate thread was woken up.
+ * If current thread is woken up, then its local queue is empty and it will poll global queue anyway,
+ * otherwise current thread may already have blocking task in its local queue.
+ */
+ val globalFirst = nextInt(2 * corePoolSize) == 0
+ if (globalFirst) globalQueue.removeFirstWithModeOrNull(TaskMode.NON_BLOCKING)?.let { return it }
+ localQueue.poll()?.let { return it }
+ if (!globalFirst) globalQueue.removeFirstOrNull()?.let { return it }
+ return trySteal()
+ }
+
+ private fun trySteal(): Task? {
+ val created = createdWorkers
+ // 0 to await an initialization and 1 to avoid excess stealing on single-core machines
+ if (created < 2) return null
+
+ // TODO to guarantee quiescence it's probably worth to do a full scan
+ var stealIndex = lastStealIndex
+ if (stealIndex == 0) stealIndex = nextInt(created) // start with random steal index
+ stealIndex++ // then go sequentially
+ if (stealIndex > created) stealIndex = 1
+ lastStealIndex = stealIndex
+ val worker = workers[stealIndex]
+ if (worker !== null && worker !== this) {
+ if (localQueue.trySteal(worker.localQueue, globalQueue)) {
+ return localQueue.poll()
+ }
+ }
+ return null
+ }
+ }
+
+ enum class WorkerState {
+ /**
+ * Has CPU token and either executes [TaskMode.NON_BLOCKING] task or tries to steal one
+ */
+ CPU_ACQUIRED,
+
+ /**
+ * Executing task with [TaskMode.PROBABLY_BLOCKING].
+ */
+ BLOCKING,
+
+ /**
+ * Currently parked.
+ */
+ PARKING,
+
+ /**
+ * Tries to execute its local work and then goes to infinite sleep as no longer needed worker.
+ */
+ RETIRING,
+
+ /**
+ * Terminal state, will no longer be used
+ */
+ TERMINATED
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
new file mode 100644
index 00000000..bd1ba95d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+/**
+ * Default instance of coroutine dispatcher.
+ */
+internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
+ val IO = blocking(systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)))
+
+ override fun close() {
+ throw UnsupportedOperationException("$DEFAULT_SCHEDULER_NAME cannot be closed")
+ }
+
+ override fun toString(): String = DEFAULT_SCHEDULER_NAME
+
+ @InternalCoroutinesApi
+ @Suppress("UNUSED")
+ public fun toDebugString(): String = super.toString()
+}
+
+/**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+// TODO make internal (and rename) after complete integration
+@InternalCoroutinesApi
+open class ExperimentalCoroutineDispatcher(
+ private val corePoolSize: Int,
+ private val maxPoolSize: Int,
+ private val idleWorkerKeepAliveNs: Long,
+ private val schedulerName: String = "CoroutineScheduler"
+) : ExecutorCoroutineDispatcher() {
+ constructor(
+ corePoolSize: Int = CORE_POOL_SIZE,
+ maxPoolSize: Int = MAX_POOL_SIZE,
+ schedulerName: String = DEFAULT_SCHEDULER_NAME
+ ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)
+
+ @Deprecated(message = "Binary compatibility for Ktor 1.0-beta", level = DeprecationLevel.HIDDEN)
+ constructor(
+ corePoolSize: Int = CORE_POOL_SIZE,
+ maxPoolSize: Int = MAX_POOL_SIZE
+ ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS)
+
+ override val executor: Executor
+ get() = coroutineScheduler
+
+ // This is variable for test purposes, so that we can reinitialize from clean state
+ private var coroutineScheduler = createScheduler()
+
+ override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
+ try {
+ coroutineScheduler.dispatch(block)
+ } catch (e: RejectedExecutionException) {
+ DefaultExecutor.dispatch(context, block)
+ }
+
+ override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit =
+ try {
+ coroutineScheduler.dispatch(block, fair = true)
+ } catch (e: RejectedExecutionException) {
+ DefaultExecutor.dispatchYield(context, block)
+ }
+
+ override fun close() = coroutineScheduler.close()
+
+ override fun toString(): String {
+ return "${super.toString()}[scheduler = $coroutineScheduler]"
+ }
+
+ /**
+ * Creates a coroutine execution context with limited parallelism to execute tasks which may potentially block.
+ * Resulting [CoroutineDispatcher] doesn't own any resources (its threads) and provides a view of the original [ExperimentalCoroutineDispatcher],
+ * giving it additional hints to adjust its behaviour.
+ *
+ * @param parallelism parallelism level, indicating how many threads can execute tasks in the resulting dispatcher parallel.
+ */
+ public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
+ require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
+ return LimitingDispatcher(this, parallelism, TaskMode.PROBABLY_BLOCKING)
+ }
+
+ /**
+ * Creates a coroutine execution context with limited parallelism to execute CPU-intensive tasks.
+ * Resulting [CoroutineDispatcher] doesn't own any resources (its threads) and provides a view of the original [ExperimentalCoroutineDispatcher],
+ * giving it additional hints to adjust its behaviour.
+ *
+ * @param parallelism parallelism level, indicating how many threads can execute tasks in the resulting dispatcher parallel.
+ */
+ public fun limited(parallelism: Int): CoroutineDispatcher {
+ require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
+ require(parallelism <= corePoolSize) { "Expected parallelism level lesser than core pool size ($corePoolSize), but have $parallelism" }
+ return LimitingDispatcher(this, parallelism, TaskMode.NON_BLOCKING)
+ }
+
+ internal fun dispatchWithContext(block: Runnable, context: TaskContext, fair: Boolean) {
+ try {
+ coroutineScheduler.dispatch(block, context, fair)
+ } catch (e: RejectedExecutionException) {
+ // Context shouldn't be lost here to properly invoke before/after task
+ DefaultExecutor.enqueue(coroutineScheduler.createTask(block, context))
+ }
+ }
+
+ private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
+
+ // fot tests only
+ @Synchronized
+ internal fun usePrivateScheduler() {
+ coroutineScheduler.shutdown(1_000L)
+ coroutineScheduler = createScheduler()
+ }
+
+ // for tests only
+ @Synchronized
+ internal fun shutdown(timeout: Long) {
+ coroutineScheduler.shutdown(timeout)
+ }
+
+ // for tests only
+ internal fun restore() = usePrivateScheduler() // recreate scheduler
+}
+
+private class LimitingDispatcher(
+ val dispatcher: ExperimentalCoroutineDispatcher,
+ val parallelism: Int,
+ override val taskMode: TaskMode
+) : ExecutorCoroutineDispatcher(), TaskContext, Executor {
+
+ private val queue = ConcurrentLinkedQueue<Runnable>()
+ private val inFlightTasks = atomic(0)
+
+ override val executor: Executor
+ get() = this
+
+ override fun execute(command: Runnable) = dispatch(command, false)
+
+ override fun close(): Unit = error("Close cannot be invoked on LimitingBlockingDispatcher")
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)
+
+ private fun dispatch(block: Runnable, fair: Boolean) {
+ var taskToSchedule = block
+ while (true) {
+ // Commit in-flight tasks slot
+ val inFlight = inFlightTasks.incrementAndGet()
+
+ // Fast path, if parallelism limit is not reached, dispatch task and return
+ if (inFlight <= parallelism) {
+ dispatcher.dispatchWithContext(taskToSchedule, this, fair)
+ return
+ }
+
+ // Parallelism limit is reached, add task to the queue
+ queue.add(taskToSchedule)
+
+ /*
+ * We're not actually scheduled anything, so rollback committed in-flight task slot:
+ * If the amount of in-flight tasks is still above the limit, do nothing
+ * If the amount of in-flight tasks is lesser than parallelism, then
+ * it's a race with a thread which finished the task from the current context, we should resubmit the first task from the queue
+ * to avoid starvation.
+ *
+ * Race example #1 (TN is N-th thread, R is current in-flight tasks number), execution is sequential:
+ *
+ * T1: submit task, start execution, R == 1
+ * T2: commit slot for next task, R == 2
+ * T1: finish T1, R == 1
+ * T2: submit next task to local queue, decrement R, R == 0
+ * Without retries, task from T2 will be stuck in the local queue
+ */
+ if (inFlightTasks.decrementAndGet() >= parallelism) {
+ return
+ }
+
+ taskToSchedule = queue.poll() ?: return
+ }
+ }
+
+ override fun toString(): String {
+ return "${super.toString()}[dispatcher = $dispatcher]"
+ }
+
+ /**
+ * Tries to dispatch tasks which were blocked due to reaching parallelism limit if there is any.
+ *
+ * Implementation note: blocking tasks are scheduled in a fair manner (to local queue tail) to avoid
+ * non-blocking continuations starvation.
+ * E.g. for
+ * ```
+ * foo()
+ * blocking()
+ * bar()
+ * ```
+ * it's more profitable to execute bar at the end of `blocking` rather than pending blocking task
+ */
+ override fun afterTask() {
+ var next = queue.poll()
+ // If we have pending tasks in current blocking context, dispatch first
+ if (next != null) {
+ dispatcher.dispatchWithContext(next, this, true)
+ return
+ }
+ inFlightTasks.decrementAndGet()
+
+ /*
+ * Re-poll again and try to submit task if it's required otherwise tasks may be stuck in the local queue.
+ * Race example #2 (TN is N-th thread, R is current in-flight tasks number), execution is sequential:
+ * T1: submit task, start execution, R == 1
+ * T2: commit slot for next task, R == 2
+ * T1: finish T1, poll queue (it's still empty), R == 2
+ * T2: submit next task to the local queue, decrement R, R == 1
+ * T1: decrement R, finish. R == 0
+ *
+ * The task from T2 is stuck is the local queue
+ */
+ next = queue.poll() ?: return
+ dispatch(next, true)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt
new file mode 100644
index 00000000..d7cb64ab
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import java.util.concurrent.*
+
+
+// TODO most of these fields will be moved to 'object ExperimentalDispatcher'
+
+internal const val DEFAULT_SCHEDULER_NAME = "DefaultDispatcher"
+
+// 100us as default
+@JvmField
+internal val WORK_STEALING_TIME_RESOLUTION_NS = systemProp(
+ "kotlinx.coroutines.scheduler.resolution.ns", 100000L
+)
+
+@JvmField
+internal val QUEUE_SIZE_OFFLOAD_THRESHOLD = systemProp(
+ "kotlinx.coroutines.scheduler.offload.threshold", 96, maxValue = BUFFER_CAPACITY
+)
+
+@JvmField
+internal val BLOCKING_DEFAULT_PARALLELISM = systemProp(
+ "kotlinx.coroutines.scheduler.blocking.parallelism", 16
+)
+
+// NOTE: we coerce default to at least two threads to give us chances that multi-threading problems
+// get reproduced even on a single-core machine, but support explicit setting of 1 thread scheduler if needed.
+@JvmField
+internal val CORE_POOL_SIZE = systemProp(
+ "kotlinx.coroutines.scheduler.core.pool.size",
+ AVAILABLE_PROCESSORS.coerceAtLeast(2), // !!! at least two here
+ minValue = CoroutineScheduler.MIN_SUPPORTED_POOL_SIZE
+)
+
+@JvmField
+internal val MAX_POOL_SIZE = systemProp(
+ "kotlinx.coroutines.scheduler.max.pool.size",
+ (AVAILABLE_PROCESSORS * 128).coerceIn(
+ CORE_POOL_SIZE,
+ CoroutineScheduler.MAX_SUPPORTED_POOL_SIZE
+ ),
+ maxValue = CoroutineScheduler.MAX_SUPPORTED_POOL_SIZE
+)
+
+@JvmField
+internal val IDLE_WORKER_KEEP_ALIVE_NS = TimeUnit.SECONDS.toNanos(
+ systemProp("kotlinx.coroutines.scheduler.keep.alive.sec", 5L)
+)
+
+@JvmField
+internal var schedulerTimeSource: TimeSource = NanoTimeSource
+
+internal enum class TaskMode {
+
+ /**
+ * Marker indicating that task is CPU-bound and will not block
+ */
+ NON_BLOCKING,
+
+ /**
+ * Marker indicating that task may potentially block, thus giving scheduler a hint that additional thread may be required
+ */
+ PROBABLY_BLOCKING,
+}
+
+internal interface TaskContext {
+ val taskMode: TaskMode
+ fun afterTask()
+}
+
+internal object NonBlockingContext : TaskContext {
+ override val taskMode: TaskMode = TaskMode.NON_BLOCKING
+
+ override fun afterTask() {
+ // Nothing for non-blocking context
+ }
+}
+
+internal abstract class Task(
+ @JvmField var submissionTime: Long,
+ @JvmField var taskContext: TaskContext
+) : Runnable {
+ constructor() : this(0, NonBlockingContext)
+ val mode: TaskMode get() = taskContext.taskMode
+}
+
+// Non-reusable Task implementation to wrap Runnable instances that do not otherwise implement task
+internal class TaskImpl(
+ @JvmField val block: Runnable,
+ submissionTime: Long,
+ taskContext: TaskContext
+) : Task(submissionTime, taskContext) {
+ override fun run() {
+ try {
+ block.run()
+ } finally {
+ taskContext.afterTask()
+ }
+ }
+
+ override fun toString(): String =
+ "Task[${block.classSimpleName}@${block.hexAddress}, $submissionTime, $taskContext]"
+}
+
+// Open for tests
+internal open class GlobalQueue : LockFreeTaskQueue<Task>(singleConsumer = false) {
+ public fun removeFirstWithModeOrNull(mode: TaskMode): Task? =
+ removeFirstOrNullIf { it.mode == mode }
+}
+
+internal abstract class TimeSource {
+ abstract fun nanoTime(): Long
+}
+
+internal object NanoTimeSource : TimeSource() {
+ 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
new file mode 100644
index 00000000..a9aa86d4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import java.util.concurrent.atomic.*
+
+internal const val BUFFER_CAPACITY_BASE = 7
+internal const val BUFFER_CAPACITY = 1 shl BUFFER_CAPACITY_BASE
+internal const val MASK = BUFFER_CAPACITY - 1 // 128 by default
+
+/**
+ * Tightly coupled with [CoroutineScheduler] queue of pending tasks, but extracted to separate file for simplicity.
+ * At any moment queue is used only by [CoroutineScheduler.Worker] threads, has only one producer (worker owning this queue)
+ * and any amount of consumers, other pool workers which are trying to steal work.
+ *
+ * ### Fairness
+ *
+ * [WorkQueue] provides semi-FIFO order, but with priority for most recently submitted task assuming
+ * that these two (current one and submitted) are communicating and sharing state thus making such communication extremely fast.
+ * E.g. submitted jobs [1, 2, 3, 4] will be executed in [4, 1, 2, 3] order.
+ *
+ * ### Work offloading
+ *
+ * When the queue is full, half of existing tasks are offloaded to global queue which is regularly polled by other pool workers.
+ * Offloading occurs in LIFO order for the sake of implementation simplicity: offloads should be extremely rare and occurs only in specific use-cases
+ * (e.g. when coroutine starts heavy fork-join-like computation), so fairness is not important.
+ * As an alternative, offloading directly to some [CoroutineScheduler.Worker] may be used, but then the strategy of selecting any idle worker
+ * should be implemented and implementation should be aware multiple producers.
+ *
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+internal class WorkQueue {
+
+ /*
+ * We read two independent counter here.
+ * Producer index is incremented only by owner
+ * Consumer index is incremented both by owner and external threads
+ *
+ * The only harmful race is:
+ * [T1] readProducerIndex (1) preemption(2) readConsumerIndex(5)
+ * [T2] changeProducerIndex (3)
+ * [T3] changeConsumerIndex (4)
+ *
+ * Which can lead to resulting size bigger than actual size at any moment of time.
+ * This is in general harmless because steal will be blocked by timer
+ */
+ internal val bufferSize: Int get() = producerIndex.value - consumerIndex.value
+
+ // TODO replace with inlined array when atomicfu will support it
+ private val buffer: AtomicReferenceArray<Task?> = AtomicReferenceArray(BUFFER_CAPACITY)
+
+ private val lastScheduledTask = atomic<Task?>(null)
+
+ private val producerIndex = atomic(0)
+ private val consumerIndex = atomic(0)
+
+ /**
+ * Retrieves and removes task from the head of the queue
+ * Invariant: this method is called only by the owner of the queue ([pollExternal] is not)
+ */
+ fun poll(): Task? =
+ lastScheduledTask.getAndSet(null) ?: pollExternal()
+
+ /**
+ * Invariant: this method is called only by the owner of the queue
+ *
+ * @param task task to put into local queue
+ * @param globalQueue fallback queue which is used when the local queue is overflown
+ * @return true if no offloading happened, false otherwise
+ */
+ fun add(task: Task, globalQueue: GlobalQueue): Boolean {
+ val previous = lastScheduledTask.getAndSet(task) ?: return true
+ return addLast(previous, globalQueue)
+ }
+
+ // Called only by the owner, returns true if no offloading happened, false otherwise
+ fun addLast(task: Task, globalQueue: GlobalQueue): Boolean {
+ var noOffloadingHappened = true
+ /*
+ * We need the loop here because race possible not only on full queue,
+ * but also on queue with one element during stealing
+ */
+ while (!tryAddLast(task)) {
+ offloadWork(globalQueue)
+ noOffloadingHappened = false
+ }
+ return noOffloadingHappened
+ }
+
+ /**
+ * Tries stealing from [victim] queue into this queue, using [globalQueue] to offload stolen tasks in case of current queue overflow.
+ *
+ * @return whether any task was stolen
+ */
+ fun trySteal(victim: WorkQueue, globalQueue: GlobalQueue): Boolean {
+ val time = schedulerTimeSource.nanoTime()
+ val bufferSize = victim.bufferSize
+ if (bufferSize == 0) return tryStealLastScheduled(time, victim, globalQueue)
+ /*
+ * Invariant: time is monotonically increasing (thanks to nanoTime), so we can stop as soon as we find the first task not satisfying a predicate.
+ * If queue size is larger than QUEUE_SIZE_OFFLOAD_THRESHOLD then unconditionally steal tasks over this limit to prevent possible queue overflow
+ */
+ var wasStolen = false
+ repeat(((bufferSize / 2).coerceAtLeast(1))) {
+ val task = victim.pollExternal { task ->
+ time - task.submissionTime >= WORK_STEALING_TIME_RESOLUTION_NS || victim.bufferSize > QUEUE_SIZE_OFFLOAD_THRESHOLD
+ }
+ ?: return wasStolen // non-local return from trySteal as we're done
+ wasStolen = true
+ add(task, globalQueue)
+ }
+ return wasStolen
+ }
+
+ private fun tryStealLastScheduled(
+ time: Long,
+ victim: WorkQueue,
+ globalQueue: GlobalQueue
+ ): Boolean {
+ val lastScheduled = victim.lastScheduledTask.value ?: return false
+ if (time - lastScheduled.submissionTime < WORK_STEALING_TIME_RESOLUTION_NS) {
+ return false
+ }
+
+ if (victim.lastScheduledTask.compareAndSet(lastScheduled, null)) {
+ add(lastScheduled, globalQueue)
+ return true
+ }
+ return false
+ }
+
+ internal fun size(): Int = if (lastScheduledTask.value != null) bufferSize + 1 else bufferSize
+
+ /**
+ * Offloads half of the current buffer to [globalQueue]
+ */
+ private fun offloadWork(globalQueue: GlobalQueue) {
+ repeat((bufferSize / 2).coerceAtLeast(1)) {
+ val task = pollExternal() ?: return
+ addToGlobalQueue(globalQueue, task)
+ }
+ }
+
+ private fun addToGlobalQueue(globalQueue: GlobalQueue, task: Task) {
+ /*
+ * globalQueue is closed as the very last step in the shutdown sequence when all worker threads had
+ * been already shutdown (with the only exception of the last worker thread that might be performing
+ * shutdown procedure itself). As a consistency check we do a [cheap!] check that it is not closed here yet.
+ */
+ check(globalQueue.addLast(task)) { "GlobalQueue could not be closed yet" }
+ }
+
+ internal fun offloadAllWork(globalQueue: GlobalQueue) {
+ lastScheduledTask.getAndSet(null)?.let { addToGlobalQueue(globalQueue, it) }
+ while (true) {
+ addToGlobalQueue(globalQueue, pollExternal() ?: return)
+ }
+ }
+
+ /**
+ * [poll] for external (not owning this queue) workers
+ */
+ private inline fun pollExternal(predicate: (Task) -> Boolean = { true }): Task? {
+ while (true) {
+ val tailLocal = consumerIndex.value
+ if (tailLocal - producerIndex.value == 0) return null
+ val index = tailLocal and MASK
+ val element = buffer[index] ?: continue
+ if (!predicate(element)) {
+ return null
+ }
+ if (consumerIndex.compareAndSet(tailLocal, tailLocal + 1)) {
+ // 1) Help GC 2) Signal producer that this slot is consumed and may be used
+ return buffer.getAndSet(index, null)
+ }
+ }
+ }
+
+ // Called only by the owner
+ private fun tryAddLast(task: Task): Boolean {
+ if (bufferSize == BUFFER_CAPACITY - 1) return false
+ val headLocal = producerIndex.value
+ val nextIndex = headLocal and MASK
+
+ /*
+ * If current element is not null then we're racing with consumers for the tail. If we skip this check then
+ * the consumer can null out current element and it will be lost. If we're racing for tail then
+ * the queue is close to overflowing => it's fine to offload work to global queue
+ */
+ if (buffer[nextIndex] != null) {
+ return false
+ }
+
+ buffer.lazySet(nextIndex, task)
+ producerIndex.incrementAndGet()
+ return true
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt
new file mode 100644
index 00000000..3a1bf3a6
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.internal.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+/**
+ * This [CoroutineContext] dispatcher can be used to simulate virtual time to speed up
+ * code, especially tests, that deal with delays and timeouts in Coroutines.
+ *
+ * Provide an instance of this TestCoroutineContext when calling the *non-blocking*
+ * [launch][CoroutineScope.launch] or [async][CoroutineScope.async]
+ * and then advance time or trigger the actions to make the co-routines execute as soon as possible.
+ *
+ * This works much like the *TestScheduler* in RxJava2, which allows to speed up tests that deal
+ * with non-blocking Rx chains that contain delays, timeouts, intervals and such.
+ *
+ * This dispatcher can also handle *blocking* coroutines that are started by [runBlocking].
+ * This dispatcher's virtual time will be automatically advanced based on the delayed actions
+ * within the Coroutine(s).
+ *
+ * **Note: This API will become obsolete in future updates due to integration with structured concurrency.**
+ * See [issue #541](https://github.com/Kotlin/kotlinx.coroutines/issues/541).
+ *
+ * @param name A user-readable name for debugging purposes.
+ */
+@Deprecated("This API has been deprecated to integrate with Structured Concurrency.",
+ ReplaceWith("TestCoroutineScope", "kotlin.coroutines.test"),
+ level = DeprecationLevel.WARNING)
+class TestCoroutineContext(private val name: String? = null) : CoroutineContext {
+ private val uncaughtExceptions = mutableListOf<Throwable>()
+
+ private val ctxDispatcher = Dispatcher()
+
+ private val ctxHandler = CoroutineExceptionHandler { _, exception ->
+ uncaughtExceptions += exception
+ }
+
+ // The ordered queue for the runnable tasks.
+ private val queue = ThreadSafeHeap<TimedRunnableObsolete>()
+
+ // The per-scheduler global order counter.
+ private var counter = 0L
+
+ // Storing time in nanoseconds internally.
+ private var time = 0L
+
+ /**
+ * Exceptions that were caught during a [launch][CoroutineScope.launch] or a [async][CoroutineScope.async] + [Deferred.await].
+ */
+ public val exceptions: List<Throwable> get() = uncaughtExceptions
+
+ // -- CoroutineContext implementation
+
+ public override fun <R> fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R =
+ operation(operation(initial, ctxDispatcher), ctxHandler)
+
+ @Suppress("UNCHECKED_CAST")
+ public override fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? = when {
+ key === ContinuationInterceptor -> ctxDispatcher as E
+ key === CoroutineExceptionHandler -> ctxHandler as E
+ else -> null
+ }
+
+ public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext = when {
+ key === ContinuationInterceptor -> ctxHandler
+ key === CoroutineExceptionHandler -> ctxDispatcher
+ else -> this
+ }
+
+ /**
+ * Returns the current virtual clock-time as it is known to this CoroutineContext.
+ *
+ * @param unit The [TimeUnit] in which the clock-time must be returned.
+ * @return The virtual clock-time
+ */
+ public fun now(unit: TimeUnit = TimeUnit.MILLISECONDS)=
+ unit.convert(time, TimeUnit.NANOSECONDS)
+
+ /**
+ * Moves the CoroutineContext's virtual clock forward by a specified amount of time.
+ *
+ * The returned delay-time can be larger than the specified delay-time if the code
+ * under test contains *blocking* Coroutines.
+ *
+ * @param delayTime The amount of time to move the CoroutineContext's clock forward.
+ * @param unit The [TimeUnit] in which [delayTime] and the return value is expressed.
+ * @return The amount of delay-time that this CoroutineContext's clock has been forwarded.
+ */
+ public fun advanceTimeBy(delayTime: Long, unit: TimeUnit = TimeUnit.MILLISECONDS): Long {
+ val oldTime = time
+ advanceTimeTo(oldTime + unit.toNanos(delayTime), TimeUnit.NANOSECONDS)
+ return unit.convert(time - oldTime, TimeUnit.NANOSECONDS)
+ }
+
+ /**
+ * Moves the CoroutineContext's clock-time to a particular moment in time.
+ *
+ * @param targetTime The point in time to which to move the CoroutineContext's clock.
+ * @param unit The [TimeUnit] in which [targetTime] is expressed.
+ */
+ fun advanceTimeTo(targetTime: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) {
+ val nanoTime = unit.toNanos(targetTime)
+ triggerActions(nanoTime)
+ if (nanoTime > time) time = nanoTime
+ }
+
+ /**
+ * Triggers any actions that have not yet been triggered and that are scheduled to be triggered at or
+ * before this CoroutineContext's present virtual clock-time.
+ */
+ public fun triggerActions() = triggerActions(time)
+
+ /**
+ * Cancels all not yet triggered actions. Be careful calling this, since it can seriously
+ * mess with your coroutines work. This method should usually be called on tear-down of a
+ * unit test.
+ */
+ public fun cancelAllActions() {
+ // An 'is-empty' test is required to avoid a NullPointerException in the 'clear()' method
+ if (!queue.isEmpty) queue.clear()
+ }
+
+ /**
+ * This method does nothing if there is one unhandled exception that satisfies the given predicate.
+ * Otherwise it throws an [AssertionError] with the given message.
+ *
+ * (this method will clear the list of unhandled exceptions)
+ *
+ * @param message Message of the [AssertionError]. Defaults to an empty String.
+ * @param predicate The predicate that must be satisfied.
+ */
+ public fun assertUnhandledException(message: String = "", predicate: (Throwable) -> Boolean) {
+ if (uncaughtExceptions.size != 1 || !predicate(uncaughtExceptions[0])) throw AssertionError(message)
+ uncaughtExceptions.clear()
+ }
+
+ /**
+ * This method does nothing if there are no unhandled exceptions or all of them satisfy the given predicate.
+ * Otherwise it throws an [AssertionError] with the given message.
+ *
+ * (this method will clear the list of unhandled exceptions)
+ *
+ * @param message Message of the [AssertionError]. Defaults to an empty String.
+ * @param predicate The predicate that must be satisfied.
+ */
+ public fun assertAllUnhandledExceptions(message: String = "", predicate: (Throwable) -> Boolean) {
+ if (!uncaughtExceptions.all(predicate)) throw AssertionError(message)
+ uncaughtExceptions.clear()
+ }
+
+ /**
+ * This method does nothing if one or more unhandled exceptions satisfy the given predicate.
+ * Otherwise it throws an [AssertionError] with the given message.
+ *
+ * (this method will clear the list of unhandled exceptions)
+ *
+ * @param message Message of the [AssertionError]. Defaults to an empty String.
+ * @param predicate The predicate that must be satisfied.
+ */
+ public fun assertAnyUnhandledException(message: String = "", predicate: (Throwable) -> Boolean) {
+ if (!uncaughtExceptions.any(predicate)) throw AssertionError(message)
+ uncaughtExceptions.clear()
+ }
+
+ /**
+ * This method does nothing if the list of unhandled exceptions satisfy the given predicate.
+ * Otherwise it throws an [AssertionError] with the given message.
+ *
+ * (this method will clear the list of unhandled exceptions)
+ *
+ * @param message Message of the [AssertionError]. Defaults to an empty String.
+ * @param predicate The predicate that must be satisfied.
+ */
+ public fun assertExceptions(message: String = "", predicate: (List<Throwable>) -> Boolean) {
+ if (!predicate(uncaughtExceptions)) throw AssertionError(message)
+ uncaughtExceptions.clear()
+ }
+
+ private fun enqueue(block: Runnable) =
+ queue.addLast(TimedRunnableObsolete(block, counter++))
+
+ private fun postDelayed(block: Runnable, delayTime: Long) =
+ TimedRunnableObsolete(block, counter++, time + TimeUnit.MILLISECONDS.toNanos(delayTime))
+ .also {
+ queue.addLast(it)
+ }
+
+ private fun processNextEvent(): Long {
+ val current = queue.peek()
+ if (current != null) {
+ // Automatically advance time for EventLoop callbacks
+ triggerActions(current.time)
+ }
+ return if (queue.isEmpty) Long.MAX_VALUE else 0L
+ }
+
+ private fun triggerActions(targetTime: Long) {
+ while (true) {
+ val current = queue.removeFirstIf { it.time <= targetTime } ?: break
+ // If the scheduled time is 0 (immediate) use current virtual time
+ if (current.time != 0L) time = current.time
+ current.run()
+ }
+ }
+
+ public override fun toString(): String = name ?: "TestCoroutineContext@$hexAddress"
+
+ private inner class Dispatcher : EventLoop(), Delay {
+ init {
+ incrementUseCount() // this event loop is never completed
+ }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ this@TestCoroutineContext.enqueue(block)
+ }
+
+ // override runBlocking to process this event loop
+ override fun shouldBeProcessedFromContext(): Boolean = true
+
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ postDelayed(Runnable {
+ with(continuation) { resumeUndispatched(Unit) }
+ }, timeMillis)
+ }
+
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val node = postDelayed(block, timeMillis)
+ return object : DisposableHandle {
+ override fun dispose() {
+ queue.remove(node)
+ }
+ }
+ }
+
+ override fun processNextEvent() = this@TestCoroutineContext.processNextEvent()
+
+ public override fun toString(): String = "Dispatcher(${this@TestCoroutineContext})"
+ }
+}
+
+private class TimedRunnableObsolete(
+ private val run: Runnable,
+ private val count: Long = 0,
+ @JvmField internal val time: Long = 0
+) : Comparable<TimedRunnableObsolete>, Runnable by run, ThreadSafeHeapNode {
+ override var heap: ThreadSafeHeap<*>? = null
+ override var index: Int = 0
+
+ override fun run() = run.run()
+
+ override fun compareTo(other: TimedRunnableObsolete) = if (time == other.time) {
+ count.compareTo(other.count)
+ } else {
+ time.compareTo(other.time)
+ }
+
+ override fun toString() = "TimedRunnable(time=$time, run=$run)"
+}
+
+/**
+ * Executes a block of code in which a unit-test can be written using the provided [TestCoroutineContext]. The provided
+ * [TestCoroutineContext] is available in the [testBody] as the `this` receiver.
+ *
+ * The [testBody] is executed and an [AssertionError] is thrown if the list of unhandled exceptions is not empty and
+ * contains any exception that is not a [CancellationException].
+ *
+ * If the [testBody] successfully executes one of the [TestCoroutineContext.assertAllUnhandledExceptions],
+ * [TestCoroutineContext.assertAnyUnhandledException], [TestCoroutineContext.assertUnhandledException] or
+ * [TestCoroutineContext.assertExceptions], the list of unhandled exceptions will have been cleared and this method will
+ * not throw an [AssertionError].
+ *
+ * **Note: This API will become obsolete in future updates due to integration with structured concurrency.**
+ * See [issue #541](https://github.com/Kotlin/kotlinx.coroutines/issues/541).
+ *
+ * @param testContext The provided [TestCoroutineContext]. If not specified, a default [TestCoroutineContext] will be
+ * provided instead.
+ * @param testBody The code of the unit-test.
+ */
+@Deprecated("This API has been deprecated to integrate with Structured Concurrency.",
+ ReplaceWith("testContext.runBlockingTest(testBody)", "kotlin.coroutines.test"),
+ level = DeprecationLevel.WARNING)
+public fun withTestContext(testContext: TestCoroutineContext = TestCoroutineContext(), testBody: TestCoroutineContext.() -> Unit) {
+ with (testContext) {
+ testBody()
+ if (!exceptions.all { it is CancellationException }) {
+ throw AssertionError("Coroutine encountered unhandled exceptions:\n$exceptions")
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt b/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt
new file mode 100644
index 00000000..dab7d5d0
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class AsyncJvmTest : TestBase() {
+ // This must be a common test but it fails on JS because of KT-21961
+ @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/jvm/test/AtomicCancellationTest.kt b/kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt
new file mode 100644
index 00000000..8a7dce01
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016-2018 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.*
+
+class AtomicCancellationTest : TestBase() {
+
+ @Test
+ fun testSendAtomicCancel() = runBlocking {
+ expect(1)
+ val channel = Channel<Int>()
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ channel.send(42) // suspends
+ expect(4) // should execute despite cancellation
+ }
+ expect(3)
+ assertEquals(42, channel.receive()) // will schedule sender for further execution
+ job.cancel() // cancel the job next
+ yield() // now yield
+ finish(5)
+ }
+
+ @Test
+ fun testSelectSendAtomicCancel() = runBlocking {
+ expect(1)
+ val channel = Channel<Int>()
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ val result = select<String> { // suspends
+ channel.onSend(42) {
+ expect(4)
+ "OK"
+ }
+ }
+ assertEquals("OK", result)
+ expect(5) // should execute despite cancellation
+ }
+ expect(3)
+ assertEquals(42, channel.receive()) // will schedule sender for further execution
+ job.cancel() // cancel the job next
+ yield() // now yield
+ finish(6)
+ }
+
+ @Test
+ fun testReceiveAtomicCancel() = runBlocking {
+ expect(1)
+ val channel = Channel<Int>()
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ assertEquals(42, channel.receive()) // suspends
+ expect(4) // should execute despite cancellation
+ }
+ expect(3)
+ channel.send(42) // will schedule receiver for further execution
+ job.cancel() // cancel the job next
+ yield() // now yield
+ finish(5)
+ }
+
+ @Test
+ fun testSelectReceiveAtomicCancel() = runBlocking {
+ expect(1)
+ val channel = Channel<Int>()
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ val result = select<String> { // suspends
+ channel.onReceive {
+ assertEquals(42, it)
+ expect(4)
+ "OK"
+ }
+ }
+ assertEquals("OK", result)
+ expect(5) // should execute despite cancellation
+ }
+ expect(3)
+ channel.send(42) // will schedule receiver for further execution
+ job.cancel() // cancel the job next
+ yield() // now yield
+ finish(6)
+ }
+
+ @Test
+ fun testSelectDeferredAwaitCancellable() = runBlocking {
+ expect(1)
+ val deferred = async { // deferred, not yet complete
+ expect(4)
+ "OK"
+ }
+ assertEquals(false, deferred.isCompleted)
+ var job: Job? = null
+ launch { // will cancel job as soon as deferred completes
+ expect(5)
+ assertEquals(true, deferred.isCompleted)
+ job!!.cancel()
+ }
+ job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ select<Unit> { // suspends
+ deferred.onAwait { expectUnreached() }
+ }
+ expectUnreached() // will not execute -- cancelled while dispatched
+ } finally {
+ finish(7) // but will execute finally blocks
+ }
+ }
+ expect(3) // continues to execute when the job suspends
+ yield() // to deferred & canceller
+ expect(6)
+ }
+
+ @Test
+ fun testSelectJobJoinCancellable() = runBlocking {
+ expect(1)
+ val jobToJoin = launch { // not yet complete
+ expect(4)
+ }
+ assertEquals(false, jobToJoin.isCompleted)
+ var job: Job? = null
+ launch { // will cancel job as soon as jobToJoin completes
+ expect(5)
+ assertEquals(true, jobToJoin.isCompleted)
+ job!!.cancel()
+ }
+ job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ try {
+ select<Unit> { // suspends
+ jobToJoin.onJoin { expectUnreached() }
+ }
+ expectUnreached() // will not execute -- cancelled while dispatched
+ } finally {
+ finish(7) // but will execute finally blocks
+ }
+ }
+ expect(3) // continues to execute when the job suspends
+ yield() // to jobToJoin & canceller
+ expect(6)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/AwaitJvmTest.kt b/kotlinx-coroutines-core/jvm/test/AwaitJvmTest.kt
new file mode 100644
index 00000000..c6b57c8f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/AwaitJvmTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+
+class AwaitJvmTest : TestBase() {
+ @Test
+ public fun testSecondLeak() = runTest {
+ // This test is to make sure that handlers installed on the second deferred do not leak
+ val d1 = CompletableDeferred<Int>()
+ val d2 = CompletableDeferred<Int>()
+ d1.completeExceptionally(TestException()) // first is crashed
+ val iterations = 3_000_000 * stressTestMultiplier
+ for (iter in 1..iterations) {
+ try {
+ awaitAll(d1, d2)
+ expectUnreached()
+ } catch (e: TestException) {
+ expect(iter)
+ }
+ }
+ finish(iterations + 1)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/AwaitStressTest.kt b/kotlinx-coroutines-core/jvm/test/AwaitStressTest.kt
new file mode 100644
index 00000000..1e53c552
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/AwaitStressTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+
+class AwaitStressTest : TestBase() {
+
+ private val iterations = 50_000 * stressTestMultiplier
+ private val pool = newFixedThreadPoolContext(4, "AwaitStressTest")
+
+ @After
+ fun tearDown() {
+ pool.close()
+ }
+
+ @Test
+ fun testMultipleExceptions() = runTest {
+ val ctx = pool + NonCancellable
+ repeat(iterations) {
+ val barrier = CyclicBarrier(4)
+ val d1 = async(ctx) {
+ barrier.await()
+ throw TestException()
+ }
+ val d2 = async(ctx) {
+ barrier.await()
+ throw TestException()
+ }
+ val d3 = async(ctx) {
+ barrier.await()
+ 1L
+ }
+ try {
+ barrier.await()
+ awaitAll(d1, d2, d3)
+ expectUnreached()
+ } catch (e: TestException) {
+ // Expected behaviour
+ }
+
+ barrier.reset()
+ }
+ }
+
+ @Test
+ fun testAwaitAll() = runTest {
+ val barrier = CyclicBarrier(3)
+ repeat(iterations) {
+ val d1 = async(pool) {
+ barrier.await()
+ 1L
+ }
+ val d2 = async(pool) {
+ barrier.await()
+ 2L
+ }
+ barrier.await()
+ awaitAll(d1, d2)
+ require(d1.isCompleted && d2.isCompleted)
+ barrier.reset()
+ }
+ }
+
+ @Test
+ fun testConcurrentCancellation() = runTest {
+ var cancelledOnce = false
+ repeat(iterations) {
+ val barrier = CyclicBarrier(3)
+
+ val d1 = async(pool) {
+ barrier.await()
+ delay(10_000)
+ yield()
+ }
+
+ val d2 = async(pool) {
+ barrier.await()
+ d1.cancel()
+ }
+
+ barrier.await()
+ try {
+ awaitAll(d1, d2)
+ } catch (e: CancellationException) {
+ cancelledOnce = true
+ }
+ }
+
+ require(cancelledOnce) { "Cancellation exception wasn't properly caught" }
+ }
+
+ @Test
+ fun testMutatingCollection() = runTest {
+ val barrier = CyclicBarrier(4)
+
+ repeat(iterations) {
+ // thread-safe collection that we are going to modify
+ val deferreds = CopyOnWriteArrayList<Deferred<Long>>()
+
+ deferreds += async(pool) {
+ barrier.await()
+ 1L
+ }
+
+ deferreds += async(pool) {
+ barrier.await()
+ 2L
+ }
+
+ deferreds += async(pool) {
+ barrier.await()
+ deferreds.removeAt(2)
+ 3L
+ }
+
+ val allJobs = ArrayList(deferreds)
+ barrier.await()
+ val results = deferreds.awaitAll() // shouldn't hang
+ check(results == listOf(1L, 2L, 3L) || results == listOf(1L, 2L))
+ allJobs.awaitAll()
+ barrier.reset()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt b/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt
new file mode 100644
index 00000000..4e25da96
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class CancellableContinuationJvmTest : TestBase() {
+ @Test
+ fun testToString() = runTest {
+ checkToString()
+ }
+
+ private suspend fun checkToString() {
+ suspendCancellableCoroutine<Unit> {
+ it.resume(Unit)
+ assertTrue(it.toString().contains("kotlinx.coroutines.CancellableContinuationJvmTest.checkToString(CancellableContinuationJvmTest.kt"))
+ }
+ suspend {}() // Eliminate tail-call optimization
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/CancellableContinuationResumeCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/CancellableContinuationResumeCloseStressTest.kt
new file mode 100644
index 00000000..7255b4ab
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/CancellableContinuationResumeCloseStressTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.atomicfu.*
+import org.junit.*
+import java.util.concurrent.*
+import kotlin.test.*
+import kotlin.test.Test
+
+class CancellableContinuationResumeCloseStressTest : TestBase() {
+ private val dispatcher =
+ newFixedThreadPoolContext(2, "CancellableContinuationResumeCloseStressTest")
+ private val startBarrier = CyclicBarrier(3)
+ private val doneBarrier = CyclicBarrier(2)
+ private val nRepeats = 1_000 * stressTestMultiplier
+
+ private val closed = atomic(false)
+ private var returnedOk = false
+
+ @After
+ fun tearDown() {
+ dispatcher.close()
+ }
+
+ @Test
+ @Suppress("BlockingMethodInNonBlockingContext")
+ fun testStress() = runTest {
+ repeat(nRepeats) {
+ closed.value = false
+ returnedOk = false
+ val job = testJob()
+ startBarrier.await()
+ job.cancel() // (1) cancel job
+ job.join()
+ // check consistency
+ doneBarrier.await()
+ if (returnedOk) {
+ assertFalse(closed.value, "should not have closed resource -- returned Ok")
+ } else {
+ assertTrue(closed.value, "should have closed resource -- was cancelled")
+ }
+ }
+ }
+
+ private fun CoroutineScope.testJob(): Job = launch(dispatcher, start = CoroutineStart.ATOMIC) {
+ val ok = resumeClose() // might be cancelled
+ assertEquals("OK", ok)
+ returnedOk = true
+ }
+
+ private suspend fun resumeClose() = suspendCancellableCoroutine<String> { cont ->
+ dispatcher.executor.execute {
+ startBarrier.await() // (2) resume at the same time
+ cont.resume("OK") {
+ close()
+ }
+ doneBarrier.await()
+ }
+ startBarrier.await() // (3) return at the same time
+ }
+
+ fun close() {
+ assertFalse(closed.getAndSet(true))
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt b/kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt
new file mode 100644
index 00000000..55c05c55
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+
+class CancelledAwaitStressTest : TestBase() {
+ private val n = 1000 * stressTestMultiplier
+
+ /**
+ * Tests that memory does not leak from cancelled [Deferred.await]
+ */
+ @Test
+ fun testCancelledAwait() = runTest {
+ val d = async {
+ delay(Long.MAX_VALUE)
+ }
+ repeat(n) {
+ val waiter = launch(start = CoroutineStart.UNDISPATCHED) {
+ val a = ByteArray(10000000) // allocate 10M of memory here
+ d.await()
+ keepMe(a) // make sure it is kept in state machine
+ }
+ waiter.cancel() // cancel await
+ yield() // complete the waiter job, release its memory
+ }
+ d.cancel() // done test
+ }
+
+ /**
+ * Tests that memory does not leak from cancelled [Job.join]
+ */
+ @Test
+ fun testCancelledJoin() = runTest {
+ val j = launch {
+ delay(Long.MAX_VALUE)
+ }
+ repeat(n) {
+ val joiner = launch(start = CoroutineStart.UNDISPATCHED) {
+ val a = ByteArray(10000000) // allocate 10M of memory here
+ j.join()
+ keepMe(a) // make sure it is kept in state machine
+ }
+ joiner.cancel() // cancel join
+ yield() // complete the joiner job, release its memory
+ }
+ j.cancel() // done test
+ }
+
+ 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/CommonPoolTest.kt b/kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt
new file mode 100644
index 00000000..9af8c285
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Assert.*
+import java.lang.reflect.*
+import java.util.concurrent.*
+
+@Suppress("DEPRECATION")
+class CommonPoolTest {
+ private inline fun <T> Try(block: () -> T) = try { block() } catch (e: Throwable) { null }
+
+ @Test
+ fun testIsGoodCommonPool() {
+ // Test only on JDKs that has all we need
+ val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") } ?: return
+ val wtfClass = Try { Class.forName("java.util.concurrent.ForkJoinPool${'$'}ForkJoinWorkerThreadFactory") } ?: return
+ val dwtfClass = Try { Class.forName("java.util.concurrent.ForkJoinPool${'$'}DefaultForkJoinWorkerThreadFactory") } ?: return
+ // We need private constructor to create "broken" FJP instance
+ val fjpCtor = Try { fjpClass.getDeclaredConstructor(
+ Int::class.java,
+ wtfClass,
+ Thread.UncaughtExceptionHandler::class.java,
+ Int::class.java,
+ String::class.java
+ ) } ?: return
+ fjpCtor.isAccessible = true
+ val dwtfCtor = Try { dwtfClass.getDeclaredConstructor() } ?: return
+ dwtfCtor.isAccessible = true
+ // Create bad pool
+ val fjp0: ExecutorService = createFJP(0, fjpCtor, dwtfCtor) ?: return
+ assertFalse(CommonPool.isGoodCommonPool(fjpClass, fjp0))
+ fjp0.shutdown()
+ // Create good pool
+ val fjp1: ExecutorService = createFJP(1, fjpCtor, dwtfCtor) ?: return
+ assertTrue(CommonPool.isGoodCommonPool(fjpClass, fjp1))
+ fjp1.shutdown()
+ }
+
+ private fun createFJP(
+ parallelism: Int,
+ fjpCtor: Constructor<out Any>,
+ dwtfCtor: Constructor<out Any>
+ ): ExecutorService? = Try {
+ fjpCtor.newInstance(
+ parallelism,
+ dwtfCtor.newInstance(),
+ Thread.getDefaultUncaughtExceptionHandler(),
+ 0,
+ "Worker"
+ )
+ } as? ExecutorService
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt b/kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt
new file mode 100644
index 00000000..12941fb1
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+
+import com.esotericsoftware.kryo.*
+import com.esotericsoftware.kryo.io.*
+import kotlinx.atomicfu.*
+import org.junit.Test
+import org.objenesis.strategy.*
+import java.io.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.reflect.*
+import kotlin.test.*
+
+@Ignore
+class ContinuationSerializationTest : TestBase() {
+
+ companion object {
+ @JvmStatic
+ var flag = false
+ }
+
+// private val atomicInt = atomic(1)
+
+ private val kryo =
+ Kryo().also { it.instantiatorStrategy = Kryo.DefaultInstantiatorStrategy(StdInstantiatorStrategy()) }
+
+ private var storage: ByteArrayOutputStream = ByteArrayOutputStream()
+
+ @Test
+ fun testSafeContinuationSerDe() = testSerization(::serialize, {
+ it.javaClass.getDeclaredField("result").apply {
+ isAccessible = true
+ set(it, COROUTINE_SUSPENDED)
+ }
+ })
+
+ @Test
+ fun testUnsafeContinuationSerDe() = testSerization(::serializeUnsafe, {})
+
+// @Test
+// fun testCancellableContinuationSerDe() = testSerization(::serializeCancellable, {
+// it.javaClass.superclass.getDeclaredField("_decision").apply {
+// isAccessible = true
+// set(it, atomicInt)
+// }
+// })
+
+ private suspend fun serialize() = suspendCoroutine<Unit> { cont ->
+ Output(storage).use {
+ kryo.writeClassAndObject(it, cont)
+ }
+ }
+
+ private suspend fun serializeCancellable() = suspendCancellableCoroutine<Unit> { cont ->
+ Output(storage).use {
+ kryo.writeClassAndObject(it, cont)
+ }
+ }
+
+ private suspend fun serializeUnsafe() = suspendCoroutineUninterceptedOrReturn<Unit> { cont ->
+ Output(storage).use {
+ kryo.writeClassAndObject(it, cont)
+ }
+ }
+
+ private fun testSerization(serialize: KSuspendFunction0<Unit>, patcher: (Continuation<Unit>) -> Unit) =
+ runBlocking {
+ launch(Unconfined) {
+ expect(1)
+ serialize()
+ flag = true
+ }
+
+ val cont = deserialise()
+ patcher(cont)
+ expect(2)
+ cont.resume(Unit)
+ finish(3)
+ assertTrue(flag)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun deserialise(): Continuation<Unit> {
+ val input = Input(ByteArrayInputStream(storage.toByteArray()))
+ input.use {
+ return kryo.readClassAndObject(it) as Continuation<Unit>
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/CoroutinesJvmTest.kt b/kotlinx-coroutines-core/jvm/test/CoroutinesJvmTest.kt
new file mode 100644
index 00000000..e890ea21
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/CoroutinesJvmTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class CoroutinesJvmTest : TestBase() {
+ @Test
+ fun testNotCancellableCodeWithExceptionCancelled() = runTest(expected = {e -> e is TestException}) {
+ expect(1)
+ // CoroutineStart.ATOMIC makes sure it will not get cancelled for it starts executing
+ val job = launch(start = CoroutineStart.ATOMIC) {
+ Thread.sleep(100) // cannot be cancelled
+ throwTestException() // will throw
+ expectUnreached()
+ }
+ expect(2)
+ job.cancel()
+ finish(3)
+ }
+
+ @Test
+ fun testCancelManyCompletedAttachedChildren() = runTest {
+ val parent = launch { /* do nothing */ }
+ val n = 10_000 * stressTestMultiplier
+ repeat(n) {
+ // create a child that already completed
+ val child = launch(start = CoroutineStart.UNDISPATCHED) { /* do nothing */ }
+ // attach it manually via internal API
+ @Suppress("DEPRECATION_ERROR")
+ parent.attachChild(child as ChildJob)
+ }
+ parent.cancelAndJoin() // cancel parent, make sure no stack overflow
+ }
+
+ private fun throwTestException(): Unit = throw TestException()
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/DebugThreadNameTest.kt b/kotlinx-coroutines-core/jvm/test/DebugThreadNameTest.kt
new file mode 100644
index 00000000..9d683960
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/DebugThreadNameTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class DebugThreadNameTest : TestBase() {
+ @BeforeTest
+ fun resetName() {
+ resetCoroutineId()
+ }
+
+ @Test
+ fun testLaunchId() = runTest {
+ assertName("coroutine#1")
+ launch {
+ assertName("coroutine#2")
+ yield()
+ assertName("coroutine#2")
+ }
+ assertName("coroutine#1")
+ }
+
+ @Test
+ fun testLaunchIdUndispatched() = runTest {
+ assertName("coroutine#1")
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ assertName("coroutine#2")
+ yield()
+ assertName("coroutine#2")
+ }
+ assertName("coroutine#1")
+ }
+
+ @Test
+ fun testLaunchName() = runTest {
+ assertName("coroutine#1")
+ launch(CoroutineName("TEST")) {
+ assertName("TEST#2")
+ yield()
+ assertName("TEST#2")
+ }
+ assertName("coroutine#1")
+ }
+
+ @Test
+ fun testWithContext() = runTest {
+ assertName("coroutine#1")
+ withContext(Dispatchers.Default) {
+ assertName("coroutine#1")
+ yield()
+ assertName("coroutine#1")
+ withContext(CoroutineName("TEST")) {
+ assertName("TEST#1")
+ yield()
+ assertName("TEST#1")
+ }
+ assertName("coroutine#1")
+ yield()
+ assertName("coroutine#1")
+ }
+ assertName("coroutine#1")
+ }
+
+ private fun assertName(expected: String) {
+ val name = Thread.currentThread().name
+ val split = name.split(Regex(" @"))
+ assertEquals(2, split.size, "Thread name '$name' is expected to contain one coroutine name")
+ assertEquals(expected, split[1], "Thread name '$name' is expected to end with coroutine name '$expected'")
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/DefaultExecutorStressTest.kt b/kotlinx-coroutines-core/jvm/test/DefaultExecutorStressTest.kt
new file mode 100644
index 00000000..b4c6aaed
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/DefaultExecutorStressTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+
+class DefaultExecutorStressTest : TestBase() {
+ @Test
+ fun testDelay() = runTest {
+ val iterations = 100_000 * stressTestMultiplier
+ withContext(DefaultExecutor) {
+ expect(1)
+ var expected = 1
+ repeat(iterations) {
+ expect(++expected)
+ val deferred = async {
+ expect(++expected)
+ val largeArray = IntArray(10_000) { it }
+ delay(Long.MAX_VALUE)
+ println(largeArray) // consume to avoid DCE, actually unreachable
+ }
+
+ expect(++expected)
+ yield()
+ deferred.cancel()
+ try {
+ deferred.await()
+ } catch (e: CancellationException) {
+ expect(++expected)
+ }
+ }
+
+ }
+ finish(2 + iterations * 4)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/DelayJvmTest.kt b/kotlinx-coroutines-core/jvm/test/DelayJvmTest.kt
new file mode 100644
index 00000000..642e5049
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/DelayJvmTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.core.*
+import org.junit.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+class DelayJvmTest : TestBase() {
+ /**
+ * Test that delay works properly in contexts with custom [ContinuationInterceptor]
+ */
+ @Test
+ fun testDelayInArbitraryContext() = runBlocking {
+ var thread: Thread? = null
+ val pool = Executors.newFixedThreadPool(1) { runnable ->
+ Thread(runnable).also { thread = it }
+ }
+ val context = CustomInterceptor(pool)
+ val c = async(context) {
+ assertThat(Thread.currentThread(), IsEqual(thread))
+ delay(100)
+ assertThat(Thread.currentThread(), IsEqual(thread))
+ 42
+ }
+ assertThat(c.await(), IsEqual(42))
+ pool.shutdown()
+ }
+
+ @Test
+ fun testDelayWithoutDispatcher() = runBlocking(CoroutineName("testNoDispatcher.main")) {
+ // launch w/o a specified dispatcher
+ val c = async(CoroutineName("testNoDispatcher.inner")) {
+ delay(100)
+ 42
+ }
+ assertThat(c.await(), IsEqual(42))
+ }
+
+ @Test
+ fun testNegativeDelay() = runBlocking {
+ expect(1)
+ val job = async {
+ expect(3)
+ delay(0)
+ expect(4)
+ }
+
+ delay(-1)
+ expect(2)
+ job.await()
+ finish(5)
+ }
+
+ class CustomInterceptor(val pool: Executor) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
+ override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
+ Wrapper(pool, continuation)
+ }
+
+ class Wrapper<T>(val pool: Executor, private val cont: Continuation<T>) : Continuation<T> {
+ override val context: CoroutineContext
+ get() = cont.context
+
+ override fun resumeWith(result: Result<T>) {
+ pool.execute { cont.resumeWith(result) }
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/EventLoopsTest.kt b/kotlinx-coroutines-core/jvm/test/EventLoopsTest.kt
new file mode 100644
index 00000000..835e6b42
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/EventLoopsTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016-2018 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 org.junit.Test
+import java.util.concurrent.locks.*
+import kotlin.concurrent.*
+import kotlin.test.*
+
+/**
+ * Tests event loops integration.
+ * See [https://github.com/Kotlin/kotlinx.coroutines/issues/860].
+ */
+class EventLoopsTest : TestBase() {
+ @Test
+ fun testNestedRunBlocking() {
+ runBlocking { // outer event loop
+ // Produce string "OK"
+ val ch = produce { send("OK") }
+ // try receive this string in a blocking way:
+ assertEquals("OK", runBlocking { ch.receive() }) // it should not hang here
+ }
+ }
+
+ @Test
+ fun testUnconfinedInRunBlocking() {
+ var completed = false
+ runBlocking {
+ launch(Dispatchers.Unconfined) {
+ completed = true
+ }
+ // should not go into runBlocking loop, but complete here
+ assertTrue(completed)
+ }
+ }
+
+ @Test
+ fun testNestedUnconfined() {
+ expect(1)
+ GlobalScope.launch(Dispatchers.Unconfined) {
+ expect(2)
+ GlobalScope.launch(Dispatchers.Unconfined) {
+ // this gets scheduled into outer unconfined loop
+ expect(4)
+ }
+ expect(3) // ^^ executed before the above unconfined
+ }
+ finish(5)
+ }
+
+ @Test
+ fun testEventLoopInDefaultExecutor() = runTest {
+ expect(1)
+ withContext(Dispatchers.Unconfined) {
+ delay(1)
+ assertTrue(Thread.currentThread().name.startsWith(DefaultExecutor.THREAD_NAME))
+ expect(2)
+ // now runBlocking inside default executor thread --> should use outer event loop
+ DefaultExecutor.enqueue(Runnable {
+ expect(4) // will execute when runBlocking runs loop
+ })
+ expect(3)
+ runBlocking {
+ expect(5)
+ }
+ }
+ finish(6)
+ }
+
+ /**
+ * Simple test for [processNextEventInCurrentThread] API use-case.
+ */
+ @Test
+ fun testProcessNextEventInCurrentThreadSimple() = runTest {
+ expect(1)
+ val event = EventSync()
+ // this coroutine fires event
+ launch {
+ expect(3)
+ event.fireEvent()
+ }
+ // main coroutine waits for event (same thread!)
+ expect(2)
+ event.blockingAwait()
+ finish(4)
+ }
+
+ @Test
+ fun testSecondThreadRunBlocking() = runTest {
+ val testThread = Thread.currentThread()
+ val testContext = coroutineContext
+ val event = EventSync() // will signal completion
+ var thread = thread {
+ runBlocking { // outer event loop
+ // Produce string "OK"
+ val ch = produce { send("OK") }
+ // try receive this string in a blocking way using test context (another thread)
+ assertEquals("OK", runBlocking(testContext) {
+ assertEquals(testThread, Thread.currentThread())
+ ch.receive() // it should not hang here
+ })
+ }
+ event.fireEvent() // done thread
+ }
+ event.blockingAwait() // wait for thread to complete
+ thread.join() // it is safe to join thread now
+ }
+
+ /**
+ * Test for [processNextEventInCurrentThread] API use-case with delay.
+ */
+ @Test
+ fun testProcessNextEventInCurrentThreadDelay() = runTest {
+ expect(1)
+ val event = EventSync()
+ // this coroutine fires event
+ launch {
+ expect(3)
+ delay(100)
+ event.fireEvent()
+ }
+ // main coroutine waits for event (same thread!)
+ expect(2)
+ event.blockingAwait()
+ finish(4)
+ }
+
+ class EventSync {
+ private val waitingThread = atomic<Thread?>(null)
+ private val fired = atomic(false)
+
+ fun fireEvent() {
+ fired.value = true
+ waitingThread.value?.let { LockSupport.unpark(it) }
+ }
+
+ fun blockingAwait() {
+ check(waitingThread.getAndSet(Thread.currentThread()) == null)
+ while (!fired.getAndSet(false)) {
+ val time = processNextEventInCurrentThread()
+ LockSupport.parkNanos(time)
+ }
+ waitingThread.value = null
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt b/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt
new file mode 100644
index 00000000..2cf43618
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.*
+import org.junit.Assert.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+class ExecutorsTest : TestBase() {
+ private fun checkThreadName(prefix: String) {
+ val name = Thread.currentThread().name
+ check(name.startsWith(prefix)) { "Expected thread name to start with '$prefix', found: '$name'" }
+ }
+
+ @Test
+ fun testSingleThread() {
+ val context = newSingleThreadContext("TestThread")
+ runBlocking(context) {
+ checkThreadName("TestThread")
+ }
+ context.close()
+ }
+
+ @Test
+ fun testFixedThreadPool() {
+ val context = newFixedThreadPoolContext(2, "TestPool")
+ runBlocking(context) {
+ checkThreadName("TestPool")
+ }
+ context.close()
+ }
+
+ @Test
+ fun testExecutorToDispatcher() {
+ val executor = Executors.newSingleThreadExecutor { r -> Thread(r, "TestExecutor") }
+ runBlocking(executor.asCoroutineDispatcher()) {
+ checkThreadName("TestExecutor")
+ }
+ executor.shutdown()
+ }
+
+ @Test
+ fun testConvertedDispatcherToExecutor() {
+ val executor: ExecutorService = Executors.newSingleThreadExecutor { r -> Thread(r, "TestExecutor") }
+ val dispatcher: CoroutineDispatcher = executor.asCoroutineDispatcher()
+ assertSame(executor, dispatcher.asExecutor())
+ executor.shutdown()
+ }
+
+ @Test
+ fun testDefaultDispatcherToExecutor() {
+ val latch = CountDownLatch(1)
+ Dispatchers.Default.asExecutor().execute {
+ checkThreadName("DefaultDispatcher")
+ latch.countDown()
+ }
+ latch.await()
+ }
+
+ @Test
+ fun testCustomDispatcherToExecutor() {
+ expect(1)
+ val dispatcher = object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ expect(2)
+ block.run()
+ }
+ }
+ val executor = dispatcher.asExecutor()
+ assertSame(dispatcher, executor.asCoroutineDispatcher())
+ executor.execute {
+ expect(3)
+ }
+ finish(4)
+ }
+
+ @Test
+ fun testTwoThreads() {
+ val ctx1 = newSingleThreadContext("Ctx1")
+ val ctx2 = newSingleThreadContext("Ctx2")
+ runBlocking(ctx1) {
+ checkThreadName("Ctx1")
+ withContext(ctx2) {
+ checkThreadName("Ctx2")
+ }
+ checkThreadName("Ctx1")
+ }
+ ctx1.close()
+ ctx2.close()
+ }
+
+ @Test
+ fun testShutdownExecutorService() {
+ val executorService = Executors.newSingleThreadExecutor { r -> Thread(r, "TestExecutor") }
+ val dispatcher = executorService.asCoroutineDispatcher()
+ runBlocking (dispatcher) {
+ checkThreadName("TestExecutor")
+ }
+ dispatcher.close()
+ check(executorService.isShutdown)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt b/kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt
new file mode 100644
index 00000000..15cb83ce
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DeferredResultUnused")
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.channels.*
+import org.junit.*
+import org.junit.Test
+import org.junit.rules.*
+import kotlin.test.*
+
+class FailFastOnStartTest : TestBase() {
+
+ @Rule
+ @JvmField
+ public val timeout: Timeout = Timeout.seconds(5)
+
+ @Test
+ fun testLaunch() = runTest(expected = ::mainException) {
+ launch(Dispatchers.Main) {}
+ }
+
+ @Test
+ fun testLaunchLazy() = runTest(expected = ::mainException) {
+ val job = launch(Dispatchers.Main, start = CoroutineStart.LAZY) { fail() }
+ job.join()
+ }
+
+ @Test
+ fun testLaunchUndispatched() = runTest(expected = ::mainException) {
+ launch(Dispatchers.Main, start = CoroutineStart.UNDISPATCHED) {
+ yield()
+ fail()
+ }
+ }
+
+ @Test
+ fun testAsync() = runTest(expected = ::mainException) {
+ async(Dispatchers.Main) {}
+ }
+
+ @Test
+ fun testAsyncLazy() = runTest(expected = ::mainException) {
+ val job = async(Dispatchers.Main, start = CoroutineStart.LAZY) { fail() }
+ job.await()
+ }
+
+ @Test
+ fun testWithContext() = runTest(expected = ::mainException) {
+ withContext(Dispatchers.Main) {
+ fail()
+ }
+ }
+
+ @Test
+ fun testProduce() = runTest(expected = ::mainException) {
+ produce<Int>(Dispatchers.Main) { fail() }
+ }
+
+ @Test
+ fun testActor() = runTest(expected = ::mainException) {
+ actor<Int>(Dispatchers.Main) { fail() }
+ }
+
+ @Test
+ fun testActorLazy() = runTest(expected = ::mainException) {
+ 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
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt b/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt
new file mode 100644
index 00000000..9bf8ffad
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.*
+import org.junit.Test
+import org.junit.runner.*
+import org.junit.runners.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+@RunWith(Parameterized::class)
+class FailingCoroutinesMachineryTest(
+ private val element: CoroutineContext.Element,
+ private val dispatcher: CoroutineDispatcher
+) : TestBase() {
+
+ private var caught: Throwable? = null
+ private val latch = CountDownLatch(1)
+ private var exceptionHandler = CoroutineExceptionHandler { _, t -> caught = t;latch.countDown() }
+ private val lazyOuterDispatcher = lazy { newFixedThreadPoolContext(1, "") }
+
+ private object FailingUpdate : ThreadContextElement<Unit> {
+ private object Key : CoroutineContext.Key<MyElement>
+
+ override val key: CoroutineContext.Key<*> get() = Key
+
+ override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) {
+ }
+
+ override fun updateThreadContext(context: CoroutineContext) {
+ throw TestException("Prevent a coroutine from starting right here for some reason")
+ }
+
+ override fun toString() = "FailingUpdate"
+ }
+
+ private object FailingRestore : ThreadContextElement<Unit> {
+ private object Key : CoroutineContext.Key<MyElement>
+
+ override val key: CoroutineContext.Key<*> get() = Key
+
+ override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) {
+ throw TestException("Prevent a coroutine from starting right here for some reason")
+ }
+
+ override fun updateThreadContext(context: CoroutineContext) {
+ }
+
+ override fun toString() = "FailingRestore"
+ }
+
+ private object ThrowingDispatcher : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ throw TestException()
+ }
+
+ override fun toString() = "ThrowingDispatcher"
+ }
+
+ private object ThrowingDispatcher2 : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ block.run()
+ }
+
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+ throw TestException()
+ }
+
+ override fun toString() = "ThrowingDispatcher2"
+ }
+
+ @After
+ fun tearDown() {
+ runCatching { (dispatcher as? ExecutorCoroutineDispatcher)?.close() }
+ if (lazyOuterDispatcher.isInitialized()) lazyOuterDispatcher.value.close()
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "Element: {0}, dispatcher: {1}")
+ fun dispatchers(): List<Array<Any>> {
+ val elements = listOf<Any>(FailingRestore, FailingUpdate)
+ val dispatchers = listOf<Any>(
+ Dispatchers.Unconfined,
+ Dispatchers.Default,
+ Executors.newFixedThreadPool(1).asCoroutineDispatcher(),
+ Executors.newScheduledThreadPool(1).asCoroutineDispatcher(),
+ ThrowingDispatcher, ThrowingDispatcher2
+ )
+
+ return elements.flatMap { element ->
+ dispatchers.map { dispatcher ->
+ arrayOf(element, dispatcher)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testElement() = runTest {
+ launch(NonCancellable + dispatcher + exceptionHandler + element) {}
+ checkException()
+ }
+
+ @Test
+ fun testNestedElement() = runTest {
+ launch(NonCancellable + dispatcher + exceptionHandler) {
+ launch(element) { }
+ }
+ checkException()
+ }
+
+ @Test
+ fun testNestedDispatcherAndElement() = runTest {
+ launch(lazyOuterDispatcher.value + NonCancellable + exceptionHandler) {
+ launch(element + dispatcher) { }
+ }
+ checkException()
+ }
+
+ private fun checkException() {
+ latch.await(2, TimeUnit.SECONDS)
+ val e = caught
+ assertNotNull(e)
+ // First condition -- failure in context element
+ val firstCondition = e is CoroutinesInternalError && e.cause is TestException
+ // Second condition -- failure from isDispatchNeeded (#880)
+ val secondCondition = e is TestException
+ assertTrue(firstCondition xor secondCondition)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/IODispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/IODispatcherTest.kt
new file mode 100644
index 00000000..fe3c71a8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/IODispatcherTest.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2018 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.test.*
+
+class IODispatcherTest : TestBase() {
+ @Test
+ fun testWithIOContext() = runTest {
+ // just a very basic test that is dispatcher works and indeed uses background thread
+ val mainThread = Thread.currentThread()
+ expect(1)
+ withContext(Dispatchers.IO) {
+ expect(2)
+ assertNotSame(mainThread, Thread.currentThread())
+ }
+
+ expect(3)
+ assertSame(mainThread, Thread.currentThread())
+ finish(4)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/JobActivationStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobActivationStressTest.kt
new file mode 100644
index 00000000..8341df92
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/JobActivationStressTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class JobActivationStressTest : TestBase() {
+ private val N_ITERATIONS = 10_000 * stressTestMultiplier
+ private val pool = newFixedThreadPoolContext(3, "JobActivationStressTest")
+
+ @After
+ fun tearDown() {
+ pool.close()
+ }
+
+ /**
+ * Perform concurrent start & cancel of a job with prior installed completion handlers
+ */
+ @Test
+ @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+ fun testActivation() = runTest {
+ val barrier = CyclicBarrier(3)
+ val scope = CoroutineScope(pool)
+ repeat(N_ITERATIONS) {
+ var wasStarted = false
+ val d = scope.async(NonCancellable, start = CoroutineStart.LAZY) {
+ wasStarted = true
+ throw TestException()
+ }
+ // need to add on completion handler
+ val causeHolder = object {
+ var cause: Throwable? = null
+ }
+ // we use synchronization on causeHolder to work around the fact that completion listeners
+ // are invoked after the job is in the final state, so when "d.join()" completes there is
+ // no guarantee that this listener was already invoked
+ d.invokeOnCompletion {
+ synchronized(causeHolder) {
+ causeHolder.cause = it ?: Error("Empty cause")
+ (causeHolder as Object).notifyAll()
+ }
+ }
+ // concurrent cancel
+ val canceller = scope.launch {
+ barrier.await()
+ d.cancel()
+ }
+ // concurrent cancel
+ val starter = scope.launch {
+ barrier.await()
+ d.start()
+ }
+ barrier.await()
+ joinAll(d, canceller, starter)
+ if (wasStarted) {
+ val exception = d.getCompletionExceptionOrNull()
+ assertTrue(exception is TestException, "exception=$exception")
+ val cause = synchronized(causeHolder) {
+ while (causeHolder.cause == null) (causeHolder as Object).wait()
+ causeHolder.cause
+ }
+ assertTrue(cause is TestException, "cause=$cause")
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt
new file mode 100644
index 00000000..107cc52c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class JobChildStressTest : TestBase() {
+ private val N_ITERATIONS = 10_000 * stressTestMultiplier
+ private val pool = newFixedThreadPoolContext(3, "JobChildStressTest")
+
+ @After
+ fun tearDown() {
+ pool.close()
+ }
+
+ /**
+ * Perform concurrent launch of a child job & cancellation of the explicit parent job
+ */
+ @Test
+ @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+ fun testChild() = runTest {
+ val barrier = CyclicBarrier(3)
+ repeat(N_ITERATIONS) {
+ var wasLaunched = false
+ var unhandledException: Throwable? = null
+ val handler = CoroutineExceptionHandler { _, ex ->
+ unhandledException = ex
+ }
+ val scope = CoroutineScope(pool + handler)
+ val parent = CompletableDeferred<Unit>()
+ // concurrent child launcher
+ val launcher = scope.launch {
+ barrier.await()
+ // A: launch child for a parent job
+ launch(parent) {
+ wasLaunched = true
+ throw TestException()
+ }
+ }
+ // concurrent cancel
+ val canceller = scope.launch {
+ barrier.await()
+ // B: cancel parent job of a child
+ parent.cancel()
+ }
+ barrier.await()
+ joinAll(launcher, canceller, parent)
+ assertNull(unhandledException)
+ if (wasLaunched) {
+ val exception = parent.getCompletionExceptionOrNull()
+ assertTrue(exception is TestException, "exception=$exception")
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt
new file mode 100644
index 00000000..3a074f15
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016-2018 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.concurrent.thread
+
+/**
+ * Tests concurrent cancel & dispose of the jobs.
+ */
+class JobDisposeStressTest: TestBase() {
+ private val TEST_DURATION = 3 * stressTestMultiplier // seconds
+
+ @Volatile
+ private var done = false
+ @Volatile
+ private var job: TestJob? = null
+ @Volatile
+ private var handle: DisposableHandle? = null
+
+ @Volatile
+ private var exception: Throwable? = null
+
+ private fun testThread(name: String, block: () -> Unit): Thread =
+ thread(start = false, name = name, block = block).apply {
+ setUncaughtExceptionHandler { t, e ->
+ exception = e
+ println("Exception in ${t.name}: $e")
+ e.printStackTrace()
+ }
+ }
+
+ @Test
+ fun testConcurrentDispose() {
+ // create threads
+ val threads = mutableListOf<Thread>()
+ threads += testThread("creator") {
+ while (!done) {
+ val job = TestJob()
+ val handle = job.invokeOnCompletion(onCancelling = true) { /* nothing */ }
+ this.job = job // post job to cancelling thread
+ this.handle = handle // post handle to concurrent disposer thread
+ handle.dispose() // dispose of handle from this thread (concurrently with other disposer)
+ }
+ }
+
+ threads += testThread("canceller") {
+ while (!done) {
+ val job = this.job ?: continue
+ job.cancel()
+ // Always returns true, TestJob never completes
+ }
+ }
+
+ threads += testThread("disposer") {
+ while (!done) {
+ handle?.dispose()
+ }
+ }
+
+ // start threads
+ threads.forEach { it.start() }
+ // wait
+ for (i in 1..TEST_DURATION) {
+ println("$i: Running")
+ Thread.sleep(1000)
+ if (exception != null) break
+ }
+ // done
+ done = true
+ // join threads
+ threads.forEach { it.join() }
+ // rethrow exception if any
+ }
+
+ @Suppress("DEPRECATION_ERROR")
+ private class TestJob : JobSupport(active = true)
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt
new file mode 100644
index 00000000..852aa2a8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.atomicfu.*
+import java.util.*
+import java.util.concurrent.*
+import kotlin.concurrent.*
+import kotlin.test.*
+
+class JobHandlersUpgradeStressTest : TestBase() {
+ private val nSeconds = 3 * stressTestMultiplier
+ private val nThreads = 4
+
+ private val cyclicBarrier = CyclicBarrier(1 + nThreads)
+ private val threads = mutableListOf<Thread>()
+
+ private val inters = atomic(0)
+ private val removed = atomic(0)
+ private val fired = atomic(0)
+
+ private val sink = atomic(0)
+
+ @Volatile
+ private var done = false
+
+ @Volatile
+ private var job: Job? = null
+
+ class State {
+ val state = atomic(0)
+ }
+
+ @Test
+ fun testStress() {
+ println("--- JobHandlersUpgradeStressTest")
+ threads += thread(name = "creator", start = false) {
+ val rnd = Random()
+ while (true) {
+ job = if (done) null else Job()
+ cyclicBarrier.await()
+ val job = job ?: break
+ // burn some time
+ repeat(rnd.nextInt(3000)) { sink.incrementAndGet() }
+ // cancel job
+ job.cancel()
+ cyclicBarrier.await()
+ inters.incrementAndGet()
+ }
+ }
+ threads += List(nThreads) { threadId ->
+ thread(name = "handler-$threadId", start = false) {
+ val rnd = Random()
+ while (true) {
+ val onCancelling = rnd.nextBoolean()
+ val invokeImmediately: Boolean = rnd.nextBoolean()
+ cyclicBarrier.await()
+ val job = job ?: break
+ val state = State()
+ // burn some time
+ repeat(rnd.nextInt(1000)) { sink.incrementAndGet() }
+ val handle =
+ job.invokeOnCompletion(onCancelling = onCancelling, invokeImmediately = invokeImmediately) {
+ if (!state.state.compareAndSet(0, 1))
+ error("Fired more than once or too late: state=${state.state.value}")
+ }
+ // burn some time
+ repeat(rnd.nextInt(1000)) { sink.incrementAndGet() }
+ // dispose
+ handle.dispose()
+ cyclicBarrier.await()
+ val resultingState = state.state.value
+ when (resultingState) {
+ 0 -> removed.incrementAndGet()
+ 1 -> fired.incrementAndGet()
+ else -> error("Cannot happen")
+ }
+ if (!state.state.compareAndSet(resultingState, 2))
+ error("Cannot fire late: resultingState=$resultingState")
+ }
+ }
+ }
+ threads.forEach { it.start() }
+ repeat(nSeconds) { second ->
+ Thread.sleep(1000)
+ println("${second + 1}: ${inters.value} iterations")
+ }
+ done = true
+ threads.forEach { it.join() }
+ println(" Completed ${inters.value} iterations")
+ println(" Removed handler ${removed.value} times")
+ println(" Fired handler ${fired.value} times")
+
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/JobStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobStressTest.kt
new file mode 100644
index 00000000..2decb3d8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/JobStressTest.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class JobStressTest : TestBase() {
+ @Test
+ fun testMemoryRelease() {
+ val job = Job()
+ val n = 10_000_000 * stressTestMultiplier
+ var fireCount = 0
+ for (i in 0 until n) job.invokeOnCompletion { fireCount++ }.dispose()
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/JobStructuredJoinStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobStructuredJoinStressTest.kt
new file mode 100644
index 00000000..ec3635ca
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/JobStructuredJoinStressTest.kt
@@ -0,0 +1,33 @@
+/*
+ * 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 a race between job failure and join.
+ *
+ * See [#1123](https://github.com/Kotlin/kotlinx.coroutines/issues/1123).
+ */
+class JobStructuredJoinStressTest : TestBase() {
+ private val nRepeats = 1_000 * stressTestMultiplier
+
+ @Test
+ fun testStress() {
+ repeat(nRepeats) {
+ assertFailsWith<TestException> {
+ runBlocking {
+ // launch in background
+ val job = launch(Dispatchers.Default) {
+ throw TestException("OK") // crash
+ }
+ assertFailsWith<CancellationException> {
+ job.join()
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/JoinStressTest.kt b/kotlinx-coroutines-core/jvm/test/JoinStressTest.kt
new file mode 100644
index 00000000..0d1a7c6c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/JoinStressTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class JoinStressTest : TestBase() {
+
+ private val iterations = 50_000 * stressTestMultiplier
+ private val pool = newFixedThreadPoolContext(3, "JoinStressTest")
+
+ @After
+ fun tearDown() {
+ pool.close()
+ }
+
+ @Test
+ fun testExceptionalJoinWithCancellation() = runBlocking {
+ val results = IntArray(2)
+
+ repeat(iterations) {
+ val barrier = CyclicBarrier(3)
+ val exceptionalJob = async(pool + NonCancellable) {
+ barrier.await()
+ throw TestException()
+ }
+
+
+ val awaiterJob = async(pool) {
+ barrier.await()
+ try {
+ exceptionalJob.await()
+ } catch (e: TestException) {
+ 0
+ } catch (e: CancellationException) {
+ 1
+ }
+ }
+
+ barrier.await()
+ exceptionalJob.cancel()
+ ++results[awaiterJob.await()]
+ }
+
+ // Check that concurrent cancellation of job which throws TestException without suspends doesn't suppress TestException
+ assertEquals(iterations, results[0], results.toList().toString())
+ assertEquals(0, results[1], results.toList().toString())
+ }
+
+ @Test
+ fun testExceptionalJoinWithMultipleCancellations() = runBlocking {
+ val results = IntArray(2)
+ var successfulCancellations = 0
+
+ repeat(iterations) {
+ val barrier = CyclicBarrier(4)
+ val exceptionalJob = async(pool + NonCancellable) {
+ barrier.await()
+ throw TestException()
+ }
+
+ val awaiterJob = async(pool) {
+ barrier.await()
+ try {
+ exceptionalJob.await()
+ } catch (e: TestException) {
+ 0
+ } catch (e: TestException1) {
+ 1
+ }
+ }
+
+ val canceller = async(pool + NonCancellable) {
+ barrier.await()
+ // cast for test purposes only
+ (exceptionalJob as AbstractCoroutine<*>).cancelInternal(TestException1())
+ }
+
+ barrier.await()
+ val awaiterResult = awaiterJob.await()
+ val cancellerResult = canceller.await()
+ if (awaiterResult == 1) {
+ assertTrue(cancellerResult)
+ }
+ ++results[awaiterResult]
+
+ if (cancellerResult) {
+ ++successfulCancellations
+ }
+ }
+
+ assertTrue(results[0] > 0, results.toList().toString())
+ assertTrue(results[1] > 0, results.toList().toString())
+ require(successfulCancellations > 0) { "Cancellation never succeeds, something wrong with stress test infra" }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt b/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt
new file mode 100644
index 00000000..d21a9f89
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class RunBlockingTest : TestBase() {
+
+ @Test
+ fun testWithTimeoutBusyWait() = runBlocking {
+ val value = withTimeoutOrNull(10) {
+ while (isActive) {
+ // Busy wait
+ }
+ "value"
+ }
+
+ assertEquals("value", value)
+ }
+
+ @Test
+ fun testPrivateEventLoop() {
+ expect(1)
+ runBlocking {
+ expect(2)
+ assertTrue(coroutineContext[ContinuationInterceptor] is EventLoop)
+ yield() // is supported!
+ expect(3)
+ }
+ finish(4)
+ }
+
+ @Test
+ fun testOuterEventLoop() {
+ expect(1)
+ runBlocking {
+ expect(2)
+ val outerEventLoop = coroutineContext[ContinuationInterceptor] as EventLoop
+ runBlocking(coroutineContext) {
+ expect(3)
+ // still same event loop
+ assertSame(coroutineContext[ContinuationInterceptor], outerEventLoop)
+ yield() // still works
+ expect(4)
+ }
+ expect(5)
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testOtherDispatcher() {
+ expect(1)
+ val name = "RunBlockingTest.testOtherDispatcher"
+ val thread = newSingleThreadContext(name)
+ runBlocking(thread) {
+ expect(2)
+ assertSame(coroutineContext[ContinuationInterceptor], thread)
+ assertTrue(Thread.currentThread().name.contains(name))
+ yield() // should work
+ expect(3)
+ }
+ finish(4)
+ thread.close()
+ }
+
+
+ @Test
+ fun testCancellation() = newFixedThreadPoolContext(2, "testCancellation").use {
+ val job = GlobalScope.launch(it) {
+ runBlocking(coroutineContext) {
+ while (true) {
+ yield()
+ }
+ }
+ }
+
+ runBlocking {
+ job.cancelAndJoin()
+ }
+ }
+
+ @Test
+ fun testCancelWithDelay() {
+ // see https://github.com/Kotlin/kotlinx.coroutines/issues/586
+ try {
+ runBlocking {
+ expect(1)
+ coroutineContext.cancel()
+ expect(2)
+ try {
+ delay(1)
+ expectUnreached()
+ } finally {
+ expect(3)
+ }
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ finish(4)
+ }
+ }
+
+ @Test(expected = CancellationException::class)
+ fun testDispatchOnShutdown() = runBlocking<Unit> {
+ expect(1)
+ val job = launch(NonCancellable) {
+ try {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ } finally {
+ finish(4)
+ }
+ }
+
+ yield()
+ expect(3)
+ coroutineContext.cancel()
+ job.cancel()
+ }
+
+ @Test(expected = CancellationException::class)
+ fun testDispatchOnShutdown2() = runBlocking<Unit> {
+ coroutineContext.cancel()
+ expect(1)
+ val job = launch(NonCancellable, start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ } finally {
+ finish(4)
+ }
+ }
+
+ expect(3)
+ job.cancel()
+ }
+
+ @Test
+ fun testNestedRunBlocking() = runBlocking {
+ delay(100)
+ val value = runBlocking {
+ delay(100)
+ runBlocking {
+ delay(100)
+ 1
+ }
+ }
+
+ assertEquals(1, value)
+ }
+
+ @Test
+ fun testIncompleteState() {
+ val handle = runBlocking {
+ // See #835
+ coroutineContext[Job]!!.invokeOnCompletion { }
+ }
+
+ handle.dispose()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt
new file mode 100644
index 00000000..01daa4a8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2016-2019 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 org.junit.*
+import java.util.*
+import java.util.concurrent.atomic.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+private val VERBOSE = systemProp("test.verbose", false)
+
+/**
+ * Base class for tests, so that tests for predictable scheduling of actions in multiple coroutines sharing a single
+ * thread can be written. Use it like this:
+ *
+ * ```
+ * class MyTest {
+ * @Test
+ * fun testSomething() = runBlocking<Unit> { // run in the context of the main thread
+ * expect(1) // initiate action counter
+ * val job = launch(context) { // use the context of the main thread
+ * expect(3) // the body of this coroutine in going to be executed in the 3rd step
+ * }
+ * expect(2) // launch just scheduled coroutine for exectuion later, so this line is executed second
+ * yield() // yield main thread to the launched job
+ * finish(4) // fourth step is the last one. `finish` must be invoked or test fails
+ * }
+ * }
+ * ```
+ */
+public actual open class TestBase actual constructor() {
+ /**
+ * Is `true` when running in a nightly stress test mode.
+ */
+ public actual val isStressTest = System.getProperty("stressTest")?.toBoolean() ?: false
+
+ public val stressTestMultiplierSqrt = if (isStressTest) 5 else 1
+
+ /**
+ * Multiply various constants in stress tests by this factor, so that they run longer during nightly stress test.
+ */
+ public actual val stressTestMultiplier = stressTestMultiplierSqrt * stressTestMultiplierSqrt
+
+ private var actionIndex = AtomicInteger()
+ private var finished = AtomicBoolean()
+ private var error = AtomicReference<Throwable>()
+
+ // Shutdown sequence
+ 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
+
+ /**
+ * 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.
+ */
+ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
+ public actual fun error(message: Any, cause: Throwable? = null): Nothing {
+ throw makeError(message, cause)
+ }
+
+ private fun makeError(message: Any, cause: Throwable? = null): IllegalStateException =
+ IllegalStateException(message.toString(), cause).also {
+ setError(it)
+ }
+
+ private fun setError(exception: Throwable) {
+ error.compareAndSet(null, exception)
+ }
+
+ private fun printError(message: String, cause: Throwable) {
+ setError(cause)
+ println("$message: $cause")
+ cause.printStackTrace(System.out)
+ println("--- Detected at ---")
+ Throwable().printStackTrace(System.out)
+ }
+
+ /**
+ * Throws [IllegalStateException] when `value` is false like `check` in stdlib, but also ensures that the
+ * test will not complete successfully even if this exception is consumed somewhere in the test.
+ */
+ public inline fun check(value: Boolean, lazyMessage: () -> Any) {
+ if (!value) error(lazyMessage())
+ }
+
+ /**
+ * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
+ */
+ public actual fun expect(index: Int) {
+ val wasIndex = actionIndex.incrementAndGet()
+ if (VERBOSE) println("expect($index), wasIndex=$wasIndex")
+ check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
+ }
+
+ /**
+ * Asserts that this line is never executed.
+ */
+ public actual fun expectUnreached() {
+ error("Should not be reached")
+ }
+
+ /**
+ * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
+ */
+ public actual fun finish(index: Int) {
+ expect(index)
+ check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" }
+ }
+
+ /**
+ * Asserts that [finish] was invoked
+ */
+ public actual fun ensureFinished() {
+ require(finished.get()) { "finish(...) should be caller prior to this check" }
+ }
+
+ public actual fun reset() {
+ check(actionIndex.get() == 0 || finished.get()) { "Expecting that 'finish(...)' was invoked, but it was not" }
+ actionIndex.set(0)
+ finished.set(false)
+ }
+
+ @Before
+ fun before() {
+ initPoolsBeforeTest()
+ threadsBefore = currentThreads()
+ originalUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
+ Thread.setDefaultUncaughtExceptionHandler { t, e ->
+ println("Exception in thread $t: $e") // The same message as in default handler
+ e.printStackTrace()
+ uncaughtExceptions.add(e)
+ }
+ }
+
+ @After
+ fun onCompletion() {
+ // onCompletion should not throw exceptions before it finishes all cleanup, so that other tests always
+ // start in a clear, restored state
+ if (actionIndex.get() != 0 && !finished.get()) {
+ makeError("Expecting that 'finish(${actionIndex.get() + 1})' was invoked, but it was not")
+ }
+ // Shutdown all thread pools
+ shutdownPoolsAfterTest()
+ // Check that that are now leftover threads
+ runCatching {
+ checkTestThreads(threadsBefore)
+ }.onFailure {
+ setError(it)
+ }
+ // Restore original uncaught exception handler
+ Thread.setDefaultUncaughtExceptionHandler(originalUncaughtExceptionHandler)
+ if (uncaughtExceptions.isNotEmpty()) {
+ makeError("Expected no uncaught exceptions, but got $uncaughtExceptions")
+ }
+ // The very last action -- throw error if any was detected
+ error.get()?.let { throw it }
+ }
+
+ fun initPoolsBeforeTest() {
+ CommonPool.usePrivatePool()
+ DefaultScheduler.usePrivateScheduler()
+ }
+
+ fun shutdownPoolsAfterTest() {
+ CommonPool.shutdown(SHUTDOWN_TIMEOUT)
+ DefaultScheduler.shutdown(SHUTDOWN_TIMEOUT)
+ DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT)
+ CommonPool.restore()
+ DefaultScheduler.restore()
+ }
+
+ @Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
+ public actual fun runTest(
+ expected: ((Throwable) -> Boolean)? = null,
+ unhandled: List<(Throwable) -> Boolean> = emptyList(),
+ block: suspend CoroutineScope.() -> Unit
+ ) {
+ var exCount = 0
+ var ex: Throwable? = null
+ try {
+ runBlocking(block = block, context = CoroutineExceptionHandler { _, e ->
+ if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
+ exCount++
+ when {
+ exCount > unhandled.size ->
+ printError("Too many unhandled exceptions $exCount, expected ${unhandled.size}, got: $e", e)
+ !unhandled[exCount - 1](e) ->
+ printError("Unhandled exception was unexpected: $e", e)
+ }
+ })
+ } catch (e: Throwable) {
+ ex = e
+ if (expected != null) {
+ if (!expected(e))
+ error("Unexpected exception: $e", e)
+ } else
+ throw e
+ } finally {
+ if (ex == null && expected != null) error("Exception was expected but none produced")
+ }
+ if (exCount < unhandled.size)
+ error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
+ }
+
+ protected inline fun <reified T: Throwable> assertFailsWith(block: () -> Unit): T {
+ val result = runCatching(block)
+ assertTrue(result.exceptionOrNull() is T, "Expected ${T::class}, but had $result")
+ return result.exceptionOrNull()!! as T
+ }
+
+ protected suspend fun currentDispatcher() = coroutineContext[ContinuationInterceptor]!!
+}
diff --git a/kotlinx-coroutines-core/jvm/test/TestBaseTest.kt b/kotlinx-coroutines-core/jvm/test/TestBaseTest.kt
new file mode 100644
index 00000000..b1981027
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/TestBaseTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+
+class TestBaseTest : TestBase() {
+ @Test
+ fun testThreadsShutdown() {
+ repeat(1000 * stressTestMultiplier) { _ ->
+ initPoolsBeforeTest()
+ val threadsBefore = currentThreads()
+ runBlocking {
+ val sub = launch {
+ delay(10000000L)
+ }
+ sub.cancel()
+ sub.join()
+ }
+ shutdownPoolsAfterTest()
+ checkTestThreads(threadsBefore)
+ }
+
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/TestSecurityManager.kt b/kotlinx-coroutines-core/jvm/test/TestSecurityManager.kt
new file mode 100644
index 00000000..e7b039ce
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/TestSecurityManager.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import java.security.Permission
+
+@Suppress("unused")
+class TestSecurityManager : SecurityManager() {
+ override fun checkPropertyAccess(key: String?) {
+ if (key?.startsWith("kotlinx.") == true)
+ throw SecurityException("'$key' property is not allowed")
+ }
+
+ override fun checkPermission(perm: Permission?) {
+ /* allow everything else */
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt
new file mode 100644
index 00000000..ea43c7ad
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016-2018 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 ThreadContextElementTest : TestBase() {
+
+ @Test
+ fun testExample() = runTest {
+ val exceptionHandler = coroutineContext[CoroutineExceptionHandler]!!
+ val mainDispatcher = coroutineContext[ContinuationInterceptor]!!
+ val mainThread = Thread.currentThread()
+ val data = MyData()
+ val element = MyElement(data)
+ assertNull(myThreadLocal.get())
+ val job = GlobalScope.launch(element + exceptionHandler) {
+ assertTrue(mainThread != Thread.currentThread())
+ assertSame(element, coroutineContext[MyElement])
+ assertSame(data, myThreadLocal.get())
+ withContext(mainDispatcher) {
+ assertSame(mainThread, Thread.currentThread())
+ assertSame(element, coroutineContext[MyElement])
+ assertSame(data, myThreadLocal.get())
+ }
+ assertTrue(mainThread != Thread.currentThread())
+ assertSame(element, coroutineContext[MyElement])
+ assertSame(data, myThreadLocal.get())
+ }
+ assertNull(myThreadLocal.get())
+ job.join()
+ assertNull(myThreadLocal.get())
+ }
+
+ @Test
+ fun testUndispatched()= runTest {
+ val exceptionHandler = coroutineContext[CoroutineExceptionHandler]!!
+ val data = MyData()
+ val element = MyElement(data)
+ val job = GlobalScope.launch(
+ context = Dispatchers.Default + exceptionHandler + element,
+ start = CoroutineStart.UNDISPATCHED
+ ) {
+ assertSame(data, myThreadLocal.get())
+ yield()
+ assertSame(data, myThreadLocal.get())
+ }
+ assertNull(myThreadLocal.get())
+ job.join()
+ assertNull(myThreadLocal.get())
+ }
+
+
+ @Test
+ fun testWithContext() = runTest {
+ expect(1)
+ newSingleThreadContext("withContext").use {
+ val data = MyData()
+ GlobalScope.async(Dispatchers.Default + MyElement(data)) {
+ assertSame(data, myThreadLocal.get())
+ expect(2)
+
+ val newData = MyData()
+ GlobalScope.async(it + MyElement(newData)) {
+ assertSame(newData, myThreadLocal.get())
+ expect(3)
+ }.await()
+
+ withContext(it + MyElement(newData)) {
+ assertSame(newData, myThreadLocal.get())
+ expect(4)
+ }
+
+ GlobalScope.async(it) {
+ assertNull(myThreadLocal.get())
+ expect(5)
+ }.await()
+
+ expect(6)
+ }.await()
+ }
+
+ finish(7)
+ }
+}
+
+class MyData
+
+// declare thread local variable holding MyData
+private val myThreadLocal = ThreadLocal<MyData?>()
+
+// declare context element holding MyData
+class MyElement(val data: MyData) : ThreadContextElement<MyData?> {
+ // declare companion object for a key of this element in coroutine context
+ companion object Key : CoroutineContext.Key<MyElement>
+
+ // provide the key of the corresponding context element
+ override val key: CoroutineContext.Key<MyElement>
+ get() = Key
+
+ // this is invoked before coroutine is resumed on current thread
+ override fun updateThreadContext(context: CoroutineContext): MyData? {
+ val oldState = myThreadLocal.get()
+ myThreadLocal.set(data)
+ return oldState
+ }
+
+ // this is invoked after coroutine has suspended on current thread
+ override fun restoreThreadContext(context: CoroutineContext, oldState: MyData?) {
+ myThreadLocal.set(oldState)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/ThreadLocalTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadLocalTest.kt
new file mode 100644
index 00000000..5d8c3d5c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ThreadLocalTest.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+import org.junit.Test
+import java.lang.IllegalStateException
+import kotlin.test.*
+
+@Suppress("RedundantAsync")
+class ThreadLocalTest : TestBase() {
+ private val stringThreadLocal = ThreadLocal<String?>()
+ private val intThreadLocal = ThreadLocal<Int?>()
+ private val executor = newFixedThreadPoolContext(1, "threadLocalTest")
+
+ @After
+ fun tearDown() {
+ executor.close()
+ }
+
+ @Test
+ fun testThreadLocal() = runTest {
+ assertNull(stringThreadLocal.get())
+ assertFalse(stringThreadLocal.isPresent())
+ val deferred = async(Dispatchers.Default + stringThreadLocal.asContextElement("value")) {
+ assertEquals("value", stringThreadLocal.get())
+ assertTrue(stringThreadLocal.isPresent())
+ withContext(executor) {
+ assertTrue(stringThreadLocal.isPresent())
+ assertFailsWith<IllegalStateException> { intThreadLocal.ensurePresent() }
+ assertEquals("value", stringThreadLocal.get())
+ }
+ assertTrue(stringThreadLocal.isPresent())
+ assertEquals("value", stringThreadLocal.get())
+ }
+
+ assertNull(stringThreadLocal.get())
+ deferred.await()
+ assertNull(stringThreadLocal.get())
+ assertFalse(stringThreadLocal.isPresent())
+ }
+
+ @Test
+ fun testThreadLocalInitialValue() = runTest {
+ intThreadLocal.set(42)
+ assertFalse(intThreadLocal.isPresent())
+ val deferred = async(Dispatchers.Default + intThreadLocal.asContextElement(239)) {
+ assertEquals(239, intThreadLocal.get())
+ withContext(executor) {
+ intThreadLocal.ensurePresent()
+ assertEquals(239, intThreadLocal.get())
+ }
+ assertEquals(239, intThreadLocal.get())
+ }
+
+ deferred.await()
+ assertEquals(42, intThreadLocal.get())
+ }
+
+ @Test
+ fun testMultipleThreadLocals() = runTest {
+ stringThreadLocal.set("test")
+ intThreadLocal.set(314)
+
+ val deferred = async(Dispatchers.Default
+ + intThreadLocal.asContextElement(value = 239) + stringThreadLocal.asContextElement(value = "pew")) {
+ assertEquals(239, intThreadLocal.get())
+ assertEquals("pew", stringThreadLocal.get())
+
+ withContext(executor) {
+ assertEquals(239, intThreadLocal.get())
+ assertEquals("pew", stringThreadLocal.get())
+ intThreadLocal.ensurePresent()
+ stringThreadLocal.ensurePresent()
+ }
+
+ assertEquals(239, intThreadLocal.get())
+ assertEquals("pew", stringThreadLocal.get())
+ }
+
+ deferred.await()
+ assertEquals(314, intThreadLocal.get())
+ assertEquals("test", stringThreadLocal.get())
+ }
+
+ @Test
+ fun testConflictingThreadLocals() = runTest {
+ intThreadLocal.set(42)
+
+ val deferred = GlobalScope.async(intThreadLocal.asContextElement(1)) {
+ assertEquals(1, intThreadLocal.get())
+
+ withContext(executor + intThreadLocal.asContextElement(42)) {
+ assertEquals(42, intThreadLocal.get())
+ }
+
+ assertEquals(1, intThreadLocal.get())
+
+ val deferred = async(intThreadLocal.asContextElement(53)) {
+ assertEquals(53, intThreadLocal.get())
+ }
+
+ deferred.await()
+ assertEquals(1, intThreadLocal.get())
+
+ val deferred2 = GlobalScope.async(executor) {
+ assertNull(intThreadLocal.get())
+ }
+
+ deferred2.await()
+ assertEquals(1, intThreadLocal.get())
+ }
+
+ deferred.await()
+ assertEquals(42, intThreadLocal.get())
+ }
+
+ @Test
+ fun testThreadLocalModification() = runTest {
+ stringThreadLocal.set("main")
+
+ val deferred = async(Dispatchers.Default
+ + stringThreadLocal.asContextElement("initial")) {
+ assertEquals("initial", stringThreadLocal.get())
+
+ stringThreadLocal.set("overridden") // <- this value is not reflected in the context, so it's not restored
+
+ withContext(executor + stringThreadLocal.asContextElement("ctx")) {
+ assertEquals("ctx", stringThreadLocal.get())
+ }
+
+ val deferred = async(stringThreadLocal.asContextElement("async")) {
+ assertEquals("async", stringThreadLocal.get())
+ }
+
+ deferred.await()
+ assertEquals("initial", stringThreadLocal.get()) // <- not restored
+ }
+
+ deferred.await()
+ assertFalse(stringThreadLocal.isPresent())
+ assertEquals("main", stringThreadLocal.get())
+ }
+
+
+
+ private data class Counter(var cnt: Int)
+ private val myCounterLocal = ThreadLocal<Counter>()
+
+ @Test
+ fun testThreadLocalModificationMutableBox() = runTest {
+ myCounterLocal.set(Counter(42))
+
+ val deferred = async(Dispatchers.Default
+ + myCounterLocal.asContextElement(Counter(0))) {
+ assertEquals(0, myCounterLocal.get().cnt)
+
+ // Mutate
+ myCounterLocal.get().cnt = 71
+
+ withContext(executor + myCounterLocal.asContextElement(Counter(-1))) {
+ assertEquals(-1, myCounterLocal.get().cnt)
+ ++myCounterLocal.get().cnt
+ }
+
+ val deferred = async(myCounterLocal.asContextElement(Counter(31))) {
+ assertEquals(31, myCounterLocal.get().cnt)
+ ++myCounterLocal.get().cnt
+ }
+
+ deferred.await()
+ assertEquals(71, myCounterLocal.get().cnt)
+ }
+
+ deferred.await()
+ assertEquals(42, myCounterLocal.get().cnt)
+ }
+
+ @Test
+ fun testWithContext() = runTest {
+ expect(1)
+ newSingleThreadContext("withContext").use {
+ val data = 42
+ GlobalScope.async(Dispatchers.Default + intThreadLocal.asContextElement(42)) {
+
+ assertEquals(data, intThreadLocal.get())
+ expect(2)
+
+ GlobalScope.async(it + intThreadLocal.asContextElement(31)) {
+ assertEquals(31, intThreadLocal.get())
+ expect(3)
+ }.await()
+
+ withContext(it + intThreadLocal.asContextElement(2)) {
+ assertEquals(2, intThreadLocal.get())
+ expect(4)
+ }
+
+ GlobalScope.async(it) {
+ assertNull(intThreadLocal.get())
+ expect(5)
+ }.await()
+
+ expect(6)
+ }.await()
+ }
+
+ finish(7)
+ }
+
+ @Test
+ fun testScope() = runTest {
+ intThreadLocal.set(42)
+ val mainThread = Thread.currentThread()
+ GlobalScope.async {
+ assertNull(intThreadLocal.get())
+ assertNotSame(mainThread, Thread.currentThread())
+ }.await()
+
+ GlobalScope.async(intThreadLocal.asContextElement()) {
+ assertEquals(42, intThreadLocal.get())
+ assertNotSame(mainThread, Thread.currentThread())
+ }.await()
+ }
+
+ @Test
+ fun testMissingThreadLocal() = runTest {
+ assertFailsWith<IllegalStateException> { stringThreadLocal.ensurePresent() }
+ assertFailsWith<IllegalStateException> { intThreadLocal.ensurePresent() }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/Threads.kt b/kotlinx-coroutines-core/jvm/test/Threads.kt
new file mode 100644
index 00000000..1d52dc6b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/Threads.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+private const val WAIT_LOST_THREADS = 10_000L // 10s
+private val ignoreLostThreads = mutableSetOf<String>()
+
+fun ignoreLostThreads(vararg s: String) { ignoreLostThreads += s }
+
+fun currentThreads(): Set<Thread> {
+ var estimate = 0
+ while (true) {
+ estimate = estimate.coerceAtLeast(Thread.activeCount() + 1)
+ val arrayOfThreads = Array<Thread?>(estimate) { null }
+ val n = Thread.enumerate(arrayOfThreads)
+ if (n >= estimate) {
+ estimate = n + 1
+ continue // retry with a better size estimate
+ }
+ val threads = hashSetOf<Thread>()
+ for (i in 0 until n)
+ threads.add(arrayOfThreads[i]!!)
+ return threads
+ }
+}
+
+fun List<Thread>.dumpThreads(header: String) {
+ println("=== $header")
+ forEach { thread ->
+ println("Thread \"${thread.name}\" ${thread.state}")
+ val trace = thread.stackTrace
+ for (t in trace) println("\tat ${t.className}.${t.methodName}(${t.fileName}:${t.lineNumber})")
+ println()
+ }
+ println("===")
+}
+
+fun ExecutorCoroutineDispatcher.dumpThreads(header: String) =
+ currentThreads().filter { it is PoolThread && it.dispatcher == this@dumpThreads }.dumpThreads(header)
+
+fun checkTestThreads(threadsBefore: Set<Thread>) {
+ // give threads some time to shutdown
+ val waitTill = System.currentTimeMillis() + WAIT_LOST_THREADS
+ var diff: List<Thread>
+ do {
+ val threadsAfter = currentThreads()
+ diff = (threadsAfter - threadsBefore).filter { thread ->
+ ignoreLostThreads.none { prefix -> thread.name.startsWith(prefix) }
+ }
+ if (diff.isEmpty()) break
+ } while (System.currentTimeMillis() <= waitTill)
+ ignoreLostThreads.clear()
+ if (diff.isEmpty()) return
+ val message = "Lost threads ${diff.map { it.name }}"
+ println("!!! $message")
+ diff.dumpThreads("Dumping lost thread stack traces")
+ error(message)
+}
diff --git a/kotlinx-coroutines-core/jvm/test/UnconfinedConcurrentStressTest.kt b/kotlinx-coroutines-core/jvm/test/UnconfinedConcurrentStressTest.kt
new file mode 100644
index 00000000..03088006
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/UnconfinedConcurrentStressTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class UnconfinedConcurrentStressTest : TestBase() {
+ private val threads = 4
+ private val executor = newFixedThreadPoolContext(threads, "UnconfinedConcurrentStressTest")
+ private val threadLocal = ThreadLocal<Int>()
+
+ @After
+ fun tearDown() {
+ executor.close()
+ }
+
+ @Test
+ fun testConcurrent() = runTest {
+ val iterations = 1_000 * stressTestMultiplier
+ val startBarrier = CyclicBarrier(threads + 1)
+ val finishLatch = CountDownLatch(threads)
+
+ repeat(threads) { id ->
+ launch(executor) {
+ startBarrier.await()
+ repeat(iterations) {
+ threadLocal.set(0)
+ launch(Dispatchers.Unconfined) {
+ assertEquals(0, threadLocal.get())
+ launch(Dispatchers.Unconfined) {
+ assertEquals(id, threadLocal.get())
+ }
+
+ threadLocal.set(id)
+ }
+ }
+
+ finishLatch.countDown()
+ }
+ }
+
+ startBarrier.await()
+ finishLatch.await()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
new file mode 100644
index 00000000..ca399f53
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import java.io.*
+import java.util.concurrent.*
+import java.util.concurrent.locks.*
+
+private const val SHUTDOWN_TIMEOUT = 1000L
+
+internal inline fun withVirtualTimeSource(log: PrintStream? = null, block: () -> Unit) {
+ DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT) // shutdown execution with old time source (in case it was working)
+ val testTimeSource = VirtualTimeSource(log)
+ timeSource = testTimeSource
+ DefaultExecutor.ensureStarted() // should start with new time source
+ try {
+ block()
+ } finally {
+ DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT)
+ testTimeSource.shutdown()
+ timeSource = null // restore time source
+ }
+}
+
+private const val NOT_PARKED = -1L
+
+private class ThreadStatus {
+ @Volatile @JvmField
+ var parkedTill = NOT_PARKED
+ @Volatile @JvmField
+ var permit = false
+ var registered = 0
+ override fun toString(): String = "parkedTill = ${TimeUnit.NANOSECONDS.toMillis(parkedTill)} ms, permit = $permit"
+}
+
+private const val MAX_WAIT_NANOS = 10_000_000_000L // 10s
+private const val REAL_TIME_STEP_NANOS = 200_000_000L // 200 ms
+private const val REAL_PARK_NANOS = 10_000_000L // 10 ms -- park for a little to better track real-time
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+internal class VirtualTimeSource(
+ private val log: PrintStream?
+) : TimeSource {
+ private val mainThread: Thread = Thread.currentThread()
+ private var checkpointNanos: Long = System.nanoTime()
+
+ @Volatile
+ private var isShutdown = false
+
+ @Volatile
+ private var time: Long = 0
+
+ private var trackedTasks = 0
+
+ private val threads = ConcurrentHashMap<Thread, ThreadStatus>()
+
+ override fun currentTimeMillis(): Long = TimeUnit.NANOSECONDS.toMillis(time)
+ override fun nanoTime(): Long = time
+
+ override fun wrapTask(block: Runnable): Runnable {
+ trackTask()
+ return Runnable {
+ try { block.run() }
+ finally { unTrackTask() }
+ }
+ }
+
+ @Synchronized
+ override fun trackTask() {
+ trackedTasks++
+ }
+
+ @Synchronized
+ override fun unTrackTask() {
+ assert(trackedTasks > 0)
+ trackedTasks--
+ }
+
+ @Synchronized
+ override fun registerTimeLoopThread() {
+ val status = threads.getOrPut(Thread.currentThread()) { ThreadStatus() }!!
+ status.registered++
+ }
+
+ @Synchronized
+ override fun unregisterTimeLoopThread() {
+ val currentThread = Thread.currentThread()
+ val status = threads[currentThread]!!
+ if (--status.registered == 0) {
+ threads.remove(currentThread)
+ wakeupAll()
+ }
+ }
+
+ override fun parkNanos(blocker: Any, nanos: Long) {
+ if (nanos <= 0) return
+ val status = threads[Thread.currentThread()]!!
+ assert(status.parkedTill == NOT_PARKED)
+ status.parkedTill = time + nanos.coerceAtMost(MAX_WAIT_NANOS)
+ while (true) {
+ checkAdvanceTime()
+ if (isShutdown || time >= status.parkedTill || status.permit) {
+ status.parkedTill = NOT_PARKED
+ status.permit = false
+ break
+ }
+ LockSupport.parkNanos(blocker, REAL_PARK_NANOS)
+ }
+ }
+
+ override fun unpark(thread: Thread) {
+ val status = threads[thread] ?: return
+ status.permit = true
+ LockSupport.unpark(thread)
+ }
+
+ @Synchronized
+ private fun checkAdvanceTime() {
+ if (isShutdown) return
+ val realNanos = System.nanoTime()
+ if (realNanos > checkpointNanos + REAL_TIME_STEP_NANOS) {
+ checkpointNanos = realNanos
+ val minParkedTill = minParkedTill()
+ time = (time + REAL_TIME_STEP_NANOS).coerceAtMost(if (minParkedTill < 0) Long.MAX_VALUE else minParkedTill)
+ logTime("R")
+ wakeupAll()
+ return
+ }
+ if (threads[mainThread] == null) return
+ if (trackedTasks != 0) return
+ val minParkedTill = minParkedTill()
+ if (minParkedTill <= time) return
+ time = minParkedTill
+ logTime("V")
+ wakeupAll()
+ }
+
+ private fun logTime(s: String) {
+ log?.println("[$s: Time = ${TimeUnit.NANOSECONDS.toMillis(time)} ms]")
+ }
+
+ private fun minParkedTill(): Long =
+ threads.values.map { if (it.permit) NOT_PARKED else it.parkedTill }.min() ?: NOT_PARKED
+
+ @Synchronized
+ fun shutdown() {
+ isShutdown = true
+ wakeupAll()
+ while (!threads.isEmpty()) (this as Object).wait()
+ }
+
+ private fun wakeupAll() {
+ threads.keys.forEach { LockSupport.unpark(it) }
+ (this as Object).notifyAll()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/WithDefaultContextTest.kt b/kotlinx-coroutines-core/jvm/test/WithDefaultContextTest.kt
new file mode 100644
index 00000000..0cad2853
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/WithDefaultContextTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class WithDefaultContextTest : TestBase() {
+ @Test
+ fun testNoSuspend() = runTest {
+ expect(1)
+ val result = withContext(Dispatchers.Default) {
+ expect(2)
+ "OK"
+ }
+ assertEquals("OK", result)
+ finish(3)
+ }
+
+ @Test
+ fun testWithSuspend() = runTest {
+ expect(1)
+ val result = withContext(Dispatchers.Default) {
+ expect(2)
+ delay(100)
+ expect(3)
+ "OK"
+ }
+ assertEquals("OK", result)
+ finish(4)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/WithTimeoutChildDipspatchStressTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutChildDipspatchStressTest.kt
new file mode 100644
index 00000000..4d440a7c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/WithTimeoutChildDipspatchStressTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2018 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.test.*
+
+class WithTimeoutChildDispatchStressTest : TestBase() {
+ private val N_REPEATS = 10_000 * stressTestMultiplier
+
+ /**
+ * This stress-test makes sure that dispatching resumption from within withTimeout
+ * works appropriately (without additional dispatch) despite the presence of
+ * children coroutine in a different dispatcher.
+ */
+ @Test
+ fun testChildDispatch() = runBlocking {
+ repeat(N_REPEATS) {
+ val result = withTimeout(5000) {
+ // child in different dispatcher
+ val job = launch(Dispatchers.Default) {
+ // done nothing, but dispatches to join from another thread
+ }
+ job.join()
+ "DONE"
+ }
+ assertEquals("DONE", result)
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt
new file mode 100644
index 00000000..5c55bd0a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class WithTimeoutOrNullJvmTest : TestBase() {
+ @Test
+ fun testOuterTimeoutFiredBeforeInner() = runTest {
+ val result = withTimeoutOrNull(100) {
+ Thread.sleep(200) // wait enough for outer timeout to fire
+ withContext(NonCancellable) { yield() } // give an event loop a chance to run and process that cancellation
+ withTimeoutOrNull(100) {
+ yield() // will cancel because of outer timeout
+ expectUnreached()
+ }
+ expectUnreached() // should not be reached, because it is outer timeout
+ }
+ // outer timeout results in null
+ assertEquals(null, result)
+ }
+
+ @Test
+ fun testIgnoredTimeout() = runTest {
+ val value = withTimeout(1) {
+ Thread.sleep(10)
+ 42
+ }
+
+ assertEquals(42, value)
+ }
+
+ @Test
+ fun testIgnoredTimeoutOnNull() = runTest {
+ val value = withTimeoutOrNull(1) {
+ Thread.sleep(10)
+ 42
+ }
+
+ assertEquals(42, value)
+ }
+
+ @Test
+ fun testIgnoredTimeoutOnNullThrowsCancellation() = runTest {
+ try {
+ withTimeoutOrNull(1) {
+ expect(1)
+ Thread.sleep(10)
+ throw CancellationException()
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testIgnoredTimeoutOnNullThrowsOnYield() = runTest {
+ val value = withTimeoutOrNull(1) {
+ Thread.sleep(10)
+ yield()
+ }
+ assertNull(value)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullThreadDispatchTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullThreadDispatchTest.kt
new file mode 100644
index 00000000..5d8c0221
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullThreadDispatchTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.CoroutineContext
+
+class WithTimeoutOrNullThreadDispatchTest : TestBase() {
+ var executor: ExecutorService? = null
+
+ @AfterTest
+ fun tearDown() {
+ executor?.shutdown()
+ }
+
+ @Test
+ fun testCancellationDispatchScheduled() {
+ checkCancellationDispatch {
+ executor = Executors.newScheduledThreadPool(1, it)
+ executor!!.asCoroutineDispatcher()
+ }
+ }
+
+ @Test
+ fun testCancellationDispatchNonScheduled() {
+ checkCancellationDispatch {
+ executor = Executors.newSingleThreadExecutor(it)
+ executor!!.asCoroutineDispatcher()
+ }
+ }
+
+ @Test
+ fun testCancellationDispatchCustomNoDelay() {
+ // it also checks that there is at most once scheduled request in flight (no spurious concurrency)
+ var error: String? = null
+ checkCancellationDispatch {
+ executor = Executors.newSingleThreadExecutor(it)
+ val scheduled = AtomicInteger(0)
+ object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ if (scheduled.incrementAndGet() > 1) error = "Two requests are scheduled concurrently"
+ executor!!.execute {
+ scheduled.decrementAndGet()
+ block.run()
+ }
+ }
+ }
+ }
+ error?.let { error(it) }
+ }
+
+ private fun checkCancellationDispatch(factory: (ThreadFactory) -> CoroutineDispatcher) = runBlocking {
+ expect(1)
+ var thread: Thread? = null
+ val dispatcher = factory(ThreadFactory { Thread(it).also { thread = it } })
+ withContext(dispatcher) {
+ expect(2)
+ assertEquals(thread, Thread.currentThread())
+ val result = withTimeoutOrNull(100) {
+ try {
+ expect(3)
+ delay(1000)
+ expectUnreached()
+ } catch (e: CancellationException) {
+ expect(4)
+ assertEquals(thread, Thread.currentThread())
+ throw e // rethrow
+ }
+ }
+ assertEquals(thread, Thread.currentThread())
+ assertEquals(null, result)
+ expect(5)
+ }
+ finish(6)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/WithTimeoutThreadDispatchTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutThreadDispatchTest.kt
new file mode 100644
index 00000000..82f5e92d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/WithTimeoutThreadDispatchTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.CoroutineContext
+
+class WithTimeoutThreadDispatchTest : TestBase() {
+ var executor: ExecutorService? = null
+
+ @AfterTest
+ fun tearDown() {
+ executor?.shutdown()
+ }
+
+ @Test
+ fun testCancellationDispatchScheduled() {
+ checkCancellationDispatch {
+ executor = Executors.newScheduledThreadPool(1, it)
+ executor!!.asCoroutineDispatcher()
+ }
+ }
+
+ @Test
+ fun testCancellationDispatchNonScheduled() {
+ checkCancellationDispatch {
+ executor = Executors.newSingleThreadExecutor(it)
+ executor!!.asCoroutineDispatcher()
+ }
+ }
+
+ @Test
+ fun testCancellationDispatchCustomNoDelay() {
+ // it also checks that there is at most once scheduled request in flight (no spurious concurrency)
+ var error: String? = null
+ checkCancellationDispatch {
+ executor = Executors.newSingleThreadExecutor(it)
+ val scheduled = AtomicInteger(0)
+ object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ if (scheduled.incrementAndGet() > 1) error = "Two requests are scheduled concurrently"
+ executor!!.execute {
+ scheduled.decrementAndGet()
+ block.run()
+ }
+ }
+ }
+ }
+ error?.let { error(it) }
+ }
+
+ private fun checkCancellationDispatch(factory: (ThreadFactory) -> CoroutineDispatcher) = runBlocking {
+ expect(1)
+ var thread: Thread? = null
+ val dispatcher = factory(ThreadFactory { Thread(it).also { thread = it } })
+ withContext(dispatcher) {
+ expect(2)
+ assertEquals(thread, Thread.currentThread())
+ try {
+ withTimeout(100) {
+ try {
+ expect(3)
+ delay(1000)
+ expectUnreached()
+ } catch (e: CancellationException) {
+ expect(4)
+ assertEquals(thread, Thread.currentThread())
+ throw e // rethrow
+ }
+ }
+ } catch (e: CancellationException) {
+ expect(5)
+ assertEquals(thread, Thread.currentThread())
+ }
+ expect(6)
+ }
+ finish(7)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt
new file mode 100644
index 00000000..1ec96ee5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+
+class ActorLazyTest : TestBase() {
+ @Test
+ fun testEmptyStart() = runBlocking {
+ expect(1)
+ val actor = actor<String>(start = CoroutineStart.LAZY) {
+ expect(5)
+ }
+ actor as Job // type assertion
+ assertThat(actor.isActive, IsEqual(false))
+ assertThat(actor.isCompleted, IsEqual(false))
+ assertThat(actor.isClosedForSend, IsEqual(false))
+ expect(2)
+ yield() // to actor code --> nothing happens (not started!)
+ assertThat(actor.isActive, IsEqual(false))
+ assertThat(actor.isCompleted, IsEqual(false))
+ assertThat(actor.isClosedForSend, IsEqual(false))
+ expect(3)
+ // start actor explicitly
+ actor.start()
+ expect(4)
+ yield() // to started actor
+ assertThat(actor.isActive, IsEqual(false))
+ assertThat(actor.isCompleted, IsEqual(true))
+ assertThat(actor.isClosedForSend, IsEqual(true))
+ finish(6)
+ }
+
+ @Test
+ fun testOne() = runBlocking {
+ expect(1)
+ val actor = actor<String>(start = CoroutineStart.LAZY) {
+ expect(4)
+ assertThat(receive(), IsEqual("OK"))
+ expect(5)
+ }
+ actor as Job // type assertion
+ assertThat(actor.isActive, IsEqual(false))
+ assertThat(actor.isCompleted, IsEqual(false))
+ assertThat(actor.isClosedForSend, IsEqual(false))
+ expect(2)
+ yield() // to actor code --> nothing happens (not started!)
+ assertThat(actor.isActive, IsEqual(false))
+ assertThat(actor.isCompleted, IsEqual(false))
+ assertThat(actor.isClosedForSend, IsEqual(false))
+ expect(3)
+ // send message to actor --> should start it
+ actor.send("OK")
+ assertThat(actor.isActive, IsEqual(false))
+ assertThat(actor.isCompleted, IsEqual(true))
+ assertThat(actor.isClosedForSend, IsEqual(true))
+ finish(6)
+ }
+
+ @Test
+ fun testCloseFreshActor() = runTest {
+ val job = launch {
+ expect(2)
+ val actor = actor<Int>(start = CoroutineStart.LAZY) {
+ expect(3)
+ for (i in channel) { }
+ expect(4)
+ }
+
+ actor.close()
+ }
+
+ expect(1)
+ job.join()
+ finish(5)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt
new file mode 100644
index 00000000..7be79832
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+import org.junit.runner.*
+import org.junit.runners.*
+import java.io.*
+
+@RunWith(Parameterized::class)
+class ActorTest(private val capacity: Int) : TestBase() {
+
+ companion object {
+ @Parameterized.Parameters(name = "Capacity: {0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = listOf(0, 1, Channel.UNLIMITED, Channel.CONFLATED).map { arrayOf<Any>(it) }
+ }
+
+ @Test
+ fun testEmpty() = runBlocking {
+ expect(1)
+ val actor = actor<String>(capacity = capacity) {
+ expect(3)
+ }
+ actor as Job // type assertion
+ assertThat(actor.isActive, IsEqual(true))
+ assertThat(actor.isCompleted, IsEqual(false))
+ assertThat(actor.isClosedForSend, IsEqual(false))
+ expect(2)
+ yield() // to actor code
+ assertThat(actor.isActive, IsEqual(false))
+ assertThat(actor.isCompleted, IsEqual(true))
+ assertThat(actor.isClosedForSend, IsEqual(true))
+ finish(4)
+ }
+
+ @Test
+ fun testOne() = runBlocking {
+ expect(1)
+ val actor = actor<String>(capacity = capacity) {
+ expect(3)
+ assertThat(receive(), IsEqual("OK"))
+ expect(6)
+ }
+ actor as Job // type assertion
+ assertThat(actor.isActive, IsEqual(true))
+ assertThat(actor.isCompleted, IsEqual(false))
+ assertThat(actor.isClosedForSend, IsEqual(false))
+ expect(2)
+ yield() // to actor code
+ assertThat(actor.isActive, IsEqual(true))
+ assertThat(actor.isCompleted, IsEqual(false))
+ assertThat(actor.isClosedForSend, IsEqual(false))
+ expect(4)
+ // send message to actor
+ actor.send("OK")
+ expect(5)
+ yield() // to actor code
+ assertThat(actor.isActive, IsEqual(false))
+ assertThat(actor.isCompleted, IsEqual(true))
+ assertThat(actor.isClosedForSend, IsEqual(true))
+ finish(7)
+ }
+
+ @Test
+ fun testCloseWithoutCause() = runTest {
+ val actor = actor<Int>(capacity = capacity) {
+ val element = channel.receiveOrNull()
+ expect(2)
+ assertEquals(42, element)
+ val next = channel.receiveOrNull()
+ assertNull(next)
+ expect(3)
+ }
+
+ expect(1)
+ actor.send(42)
+ yield()
+ actor.close()
+ yield()
+ finish(4)
+ }
+
+ @Test
+ fun testCloseWithCause() = runTest {
+ val actor = actor<Int>(capacity = capacity) {
+ val element = channel.receiveOrNull()
+ expect(2)
+ require(element!! == 42)
+ try {
+ channel.receiveOrNull()
+ } catch (e: IOException) {
+ expect(3)
+ }
+ }
+
+ expect(1)
+ actor.send(42)
+ yield()
+ actor.close(IOException())
+ yield()
+ finish(4)
+ }
+
+ @Test
+ fun testCancelEnclosingJob() = runTest {
+ val job = async {
+ actor<Int>(capacity = capacity) {
+ expect(1)
+ channel.receiveOrNull()
+ expectUnreached()
+ }
+ }
+
+ yield()
+ yield()
+
+ expect(2)
+ yield()
+ job.cancel()
+
+ try {
+ job.await()
+ expectUnreached()
+ } catch (e: CancellationException) {
+ assertTrue(e.message?.contains("Job was cancelled") ?: false)
+ }
+
+ finish(3)
+ }
+
+ @Test
+ fun testThrowingActor() = runTest(unhandled = listOf({e -> e is IllegalArgumentException})) {
+ val parent = Job()
+ val actor = actor<Int>(parent) {
+ channel.consumeEach {
+ expect(1)
+ throw IllegalArgumentException()
+ }
+ }
+
+ actor.send(1)
+ parent.cancel()
+ parent.join()
+ finish(2)
+ }
+
+ @Test
+ fun testChildJob() = runTest {
+ val parent = Job()
+ actor<Int>(parent) {
+ launch {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(1)
+ }
+ }
+ }
+
+ yield()
+ yield()
+ parent.cancel()
+ parent.join()
+ finish(2)
+ }
+
+ @Test
+ fun testCloseFreshActor() = runTest {
+ for (start in CoroutineStart.values()) {
+ val job = launch {
+ val actor = actor<Int>(start = start) { for (i in channel) {} }
+ actor.close()
+ }
+
+ job.join()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt
new file mode 100644
index 00000000..74dc24c7
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016-2018 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.*
+import org.junit.runner.*
+import org.junit.runners.*
+
+@RunWith(Parameterized::class)
+class ArrayChannelStressTest(private val capacity: Int) : TestBase() {
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}, nSenders={1}, nReceivers={2}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = listOf(1, 10, 100, 100_000, 1_000_000).map { arrayOf<Any>(it) }
+ }
+
+ @Test
+ fun testStress() = runTest {
+ val n = 100_000 * stressTestMultiplier
+ val q = Channel<Int>(capacity)
+ val sender = launch {
+ for (i in 1..n) {
+ q.send(i)
+ }
+ expect(2)
+ }
+ val receiver = launch {
+ for (i in 1..n) {
+ val next = q.receive()
+ check(next == i)
+ }
+ expect(3)
+ }
+ expect(1)
+ sender.join()
+ receiver.join()
+ finish(4)
+ }
+
+ @Test
+ fun testBurst() = runTest {
+ Assume.assumeTrue(capacity < 100_000)
+ repeat(10_000 * stressTestMultiplier) {
+ val channel = Channel<Int>(capacity)
+ val sender = launch(Dispatchers.Default) {
+ for (i in 1..capacity * 2) {
+ channel.send(i)
+ }
+ }
+ val receiver = launch(Dispatchers.Default) {
+ for (i in 1..capacity * 2) {
+ val next = channel.receive()
+ check(next == i)
+ }
+ }
+ sender.join()
+ receiver.join()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt
new file mode 100644
index 00000000..54ba7b63
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.selects.*
+import org.junit.*
+import org.junit.runner.*
+import org.junit.runners.*
+import java.util.concurrent.atomic.*
+
+/**
+ * Tests delivery of events to multiple broadcast channel subscribers.
+ */
+@RunWith(Parameterized::class)
+class BroadcastChannelMultiReceiveStressTest(
+ private val kind: TestBroadcastChannelKind
+) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> =
+ TestBroadcastChannelKind.values().map { arrayOf<Any>(it) }
+ }
+
+ private val nReceivers = if (isStressTest) 10 else 5
+ private val nSeconds = 3 * stressTestMultiplier
+
+ private val broadcast = kind.create<Long>()
+ private val pool = newFixedThreadPoolContext(nReceivers + 1, "BroadcastChannelMultiReceiveStressTest")
+
+ private val sentTotal = AtomicLong()
+ private val receivedTotal = AtomicLong()
+ private val stopOnReceive = AtomicLong(-1)
+ private val lastReceived = Array(nReceivers) { AtomicLong(-1) }
+
+ @After
+ fun tearDown() {
+ pool.close()
+ }
+
+ @Test
+ fun testStress() = runBlocking {
+ println("--- BroadcastChannelMultiReceiveStressTest $kind with nReceivers=$nReceivers")
+ val sender =
+ launch(pool + CoroutineName("Sender")) {
+ var i = 0L
+ while (isActive) {
+ broadcast.send(++i)
+ sentTotal.set(i) // set sentTotal only if `send` was not cancelled
+ }
+ }
+ val receivers = mutableListOf<Job>()
+ fun printProgress() {
+ println("Sent ${sentTotal.get()}, received ${receivedTotal.get()}, receivers=${receivers.size}")
+ }
+ // ramp up receivers
+ repeat(nReceivers) {
+ delay(100) // wait 0.1 sec
+ val receiverIndex = receivers.size
+ val name = "Receiver$receiverIndex"
+ println("Launching $name")
+ receivers += launch(pool + CoroutineName(name)) {
+ val channel = broadcast.openSubscription()
+ when (receiverIndex % 5) {
+ 0 -> doReceive(channel, receiverIndex)
+ 1 -> doReceiveOrNull(channel, receiverIndex)
+ 2 -> doIterator(channel, receiverIndex)
+ 3 -> doReceiveSelect(channel, receiverIndex)
+ 4 -> doReceiveSelectOrNull(channel, receiverIndex)
+ }
+ channel.cancel()
+ }
+ printProgress()
+ }
+ // wait
+ repeat(nSeconds) { _ ->
+ delay(1000)
+ printProgress()
+ }
+ sender.cancelAndJoin()
+ println("Tested $kind with nReceivers=$nReceivers")
+ val total = sentTotal.get()
+ println(" Sent $total events, waiting for receivers")
+ stopOnReceive.set(total)
+ try {
+ withTimeout(5000) {
+ receivers.forEachIndexed { index, receiver ->
+ if (lastReceived[index].get() == total)
+ receiver.cancel()
+ else
+ receiver.join()
+ }
+ }
+ } catch (e: Exception) {
+ println("Failed: $e")
+ pool.dumpThreads("Threads in pool")
+ receivers.indices.forEach { index ->
+ println("lastReceived[$index] = ${lastReceived[index].get()}")
+ }
+ throw e
+ }
+ println(" Received ${receivedTotal.get()} events")
+ }
+
+ private fun doReceived(receiverIndex: Int, i: Long): Boolean {
+ val last = lastReceived[receiverIndex].get()
+ check(i > last) { "Last was $last, got $i" }
+ if (last != -1L && !kind.isConflated)
+ check(i == last + 1) { "Last was $last, got $i" }
+ receivedTotal.incrementAndGet()
+ lastReceived[receiverIndex].set(i)
+ return i == stopOnReceive.get()
+ }
+
+ private suspend fun doReceive(channel: ReceiveChannel<Long>, receiverIndex: Int) {
+ while (true) {
+ try {
+ val stop = doReceived(receiverIndex, channel.receive())
+ if (stop) break
+ }
+ catch (ex: ClosedReceiveChannelException) { break }
+ }
+ }
+
+ private suspend fun doReceiveOrNull(channel: ReceiveChannel<Long>, receiverIndex: Int) {
+ while (true) {
+ val stop = doReceived(receiverIndex, channel.receiveOrNull() ?: break)
+ if (stop) break
+ }
+ }
+
+ private suspend fun doIterator(channel: ReceiveChannel<Long>, receiverIndex: Int) {
+ for (event in channel) {
+ val stop = doReceived(receiverIndex, event)
+ if (stop) break
+ }
+ }
+
+ private suspend fun doReceiveSelect(channel: ReceiveChannel<Long>, receiverIndex: Int) {
+ while (true) {
+ try {
+ val event = select<Long> { channel.onReceive { it } }
+ val stop = doReceived(receiverIndex, event)
+ if (stop) break
+ } catch (ex: ClosedReceiveChannelException) { break }
+ }
+ }
+
+ private suspend fun doReceiveSelectOrNull(channel: ReceiveChannel<Long>, receiverIndex: Int) {
+ while (true) {
+ val event = select<Long?> { channel.onReceiveOrNull { it } } ?: break
+ val stop = doReceived(receiverIndex, event)
+ if (stop) break
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt
new file mode 100644
index 00000000..221120af
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-2018 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.*
+import org.junit.runner.*
+import org.junit.runners.*
+import java.util.concurrent.atomic.*
+
+/**
+ * Creates a broadcast channel and repeatedly opens new subscription, receives event, closes it,
+ * to stress test the logic of opening the subscription
+ * to broadcast channel while events are being concurrently sent to it.
+ */
+@RunWith(Parameterized::class)
+class BroadcastChannelSubStressTest(
+ private val kind: TestBroadcastChannelKind
+) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> =
+ TestBroadcastChannelKind.values().map { arrayOf<Any>(it) }
+ }
+
+ private val nSeconds = 5 * stressTestMultiplier
+ private val broadcast = kind.create<Long>()
+
+ private val sentTotal = AtomicLong()
+ private val receivedTotal = AtomicLong()
+
+ @Test
+ fun testStress() = runBlocking {
+ println("--- BroadcastChannelSubStressTest $kind")
+ val sender =
+ launch(context = Dispatchers.Default + CoroutineName("Sender")) {
+ while (isActive) {
+ broadcast.send(sentTotal.incrementAndGet())
+ }
+ }
+ val receiver =
+ launch(context = Dispatchers.Default + CoroutineName("Receiver")) {
+ var last = -1L
+ while (isActive) {
+ val channel = broadcast.openSubscription()
+ val i = channel.receive()
+ check(i >= last) { "Last was $last, got $i" }
+ if (!kind.isConflated) check(i != last) { "Last was $last, got it again" }
+ receivedTotal.incrementAndGet()
+ last = i
+ channel.cancel()
+ }
+ }
+ var prevSent = -1L
+ repeat(nSeconds) { sec ->
+ delay(1000)
+ val curSent = sentTotal.get()
+ println("${sec + 1}: Sent $curSent, received ${receivedTotal.get()}")
+ check(curSent > prevSent) { "Send stalled at $curSent events" }
+ prevSent = curSent
+ }
+ withTimeout(5000) {
+ sender.cancelAndJoin()
+ receiver.cancelAndJoin()
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelAtomicCancelStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelAtomicCancelStressTest.kt
new file mode 100644
index 00000000..6223213d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelAtomicCancelStressTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.selects.*
+import org.junit.*
+import org.junit.Assert.*
+import org.junit.runner.*
+import org.junit.runners.*
+import java.util.*
+import java.util.concurrent.atomic.*
+
+/**
+ * Tests cancel atomicity for channel send & receive operations, including their select versions.
+ */
+@RunWith(Parameterized::class)
+class ChannelAtomicCancelStressTest(private val kind: TestChannelKind) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = TestChannelKind.values().map { arrayOf<Any>(it) }
+ }
+
+ private val TEST_DURATION = 1000L * stressTestMultiplier
+
+ private val dispatcher = newFixedThreadPoolContext(2, "ChannelAtomicCancelStressTest")
+ private val scope = CoroutineScope(dispatcher)
+
+ private val channel = kind.create()
+ private val senderDone = Channel<Boolean>(1)
+ private val receiverDone = Channel<Boolean>(1)
+
+ private var lastSent = 0
+ private var lastReceived = 0
+
+ private var stoppedSender = 0
+ private var stoppedReceiver = 0
+
+ private var missedCnt = 0
+ private var dupCnt = 0
+
+ val failed = AtomicReference<Throwable>()
+
+ lateinit var sender: Job
+ lateinit var receiver: Job
+
+ @After
+ fun tearDown() {
+ dispatcher.close()
+ }
+
+ fun fail(e: Throwable) = failed.compareAndSet(null, e)
+
+ private inline fun cancellable(done: Channel<Boolean>, block: () -> Unit) {
+ try {
+ block()
+ } catch (e: Throwable) {
+ if (e !is CancellationException) fail(e)
+ throw e
+ } finally {
+ if (!done.offer(true))
+ fail(IllegalStateException("failed to offer to done channel"))
+ }
+ }
+
+ @Test
+ fun testAtomicCancelStress() = runBlocking {
+ println("--- ChannelAtomicCancelStressTest $kind")
+ val deadline = System.currentTimeMillis() + TEST_DURATION
+ launchSender()
+ launchReceiver()
+ val rnd = Random()
+ while (System.currentTimeMillis() < deadline && failed.get() == null) {
+ when (rnd.nextInt(3)) {
+ 0 -> { // cancel & restart sender
+ stopSender()
+ launchSender()
+ }
+ 1 -> { // cancel & restart receiver
+ stopReceiver()
+ launchReceiver()
+ }
+ 2 -> yield() // just yield (burn a little time)
+ }
+ }
+ stopSender()
+ stopReceiver()
+ println(" Sent $lastSent ints to channel")
+ println(" Received $lastReceived ints from channel")
+ println(" Stopped sender $stoppedSender times")
+ println("Stopped receiver $stoppedReceiver times")
+ println(" Missed $missedCnt ints")
+ println(" Duplicated $dupCnt ints")
+ failed.get()?.let { throw it }
+ assertEquals(0, dupCnt)
+ if (!kind.isConflated) {
+ assertEquals(0, missedCnt)
+ assertEquals(lastSent, lastReceived)
+ }
+ }
+
+ private fun launchSender() {
+ sender = scope.launch(start = CoroutineStart.ATOMIC) {
+ val rnd = Random()
+ cancellable(senderDone) {
+ var counter = 0
+ while (true) {
+ val trySend = lastSent + 1
+ when (rnd.nextInt(2)) {
+ 0 -> channel.send(trySend)
+ 1 -> select { channel.onSend(trySend) {} }
+ else -> error("cannot happen")
+ }
+ lastSent = trySend // update on success
+ when {
+ // must artificially slow down LINKED_LIST sender to avoid overwhelming receiver and going OOM
+ kind == TestChannelKind.LINKED_LIST -> while (lastSent > lastReceived + 100) yield()
+ // yield periodically to check cancellation on conflated channels
+ kind.isConflated -> if (counter++ % 100 == 0) yield()
+ }
+ }
+ }
+ }
+ }
+
+ private suspend fun stopSender() {
+ stoppedSender++
+ sender.cancel()
+ senderDone.receive()
+ }
+
+ private fun launchReceiver() {
+ receiver = scope.launch(start = CoroutineStart.ATOMIC) {
+ val rnd = Random()
+ cancellable(receiverDone) {
+ while (true) {
+ val received = when (rnd.nextInt(2)) {
+ 0 -> channel.receive()
+ 1 -> select { channel.onReceive { it } }
+ else -> error("cannot happen")
+ }
+ val expected = lastReceived + 1
+ if (received > expected)
+ missedCnt++
+ if (received < expected)
+ dupCnt++
+ lastReceived = received
+ }
+ }
+ }
+ }
+
+ private suspend fun stopReceiver() {
+ stoppedReceiver++
+ receiver.cancel()
+ receiverDone.receive()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
new file mode 100644
index 00000000..67bd68ac
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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()
+ }
+
+ @Test
+ fun testConflatedLockFreedom() {
+ // This test does not really verify that all sent elements were received
+ // and checks only LF property
+ channel = Channel(Channel.CONFLATED)
+ performLockFreedomTest()
+ }
+
+ private fun performLockFreedomTest() {
+ env.onCompletion { channel.close() }
+ repeat(2) { env.testThread { sender() } }
+ repeat(2) { env.testThread { 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: ClosedSendChannelException) {
+ 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: ClosedReceiveChannelException) {
+ 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
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt
new file mode 100644
index 00000000..1bd6060a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.selects.*
+import org.junit.*
+import org.junit.Assert.*
+import org.junit.runner.*
+import org.junit.runners.*
+import java.util.concurrent.atomic.*
+
+@RunWith(Parameterized::class)
+class ChannelSendReceiveStressTest(
+ private val kind: TestChannelKind,
+ private val nSenders: Int,
+ private val nReceivers: Int
+) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "{0}, nSenders={1}, nReceivers={2}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> =
+ listOf(1, 2, 10).flatMap { nSenders ->
+ listOf(1, 10).flatMap { nReceivers ->
+ TestChannelKind.values().map { arrayOf(it, nSenders, nReceivers) }
+ }
+ }
+ }
+
+ private val timeLimit = 30_000L * stressTestMultiplier // 30 sec
+ private val nEvents = 200_000 * stressTestMultiplier
+
+ private val maxBuffer = 10_000 // artificial limit for LinkedListChannel
+
+ val channel = kind.create()
+ private val sendersCompleted = AtomicInteger()
+ private val receiversCompleted = AtomicInteger()
+ private val dupes = AtomicInteger()
+ private val sentTotal = AtomicInteger()
+ val received = AtomicIntegerArray(nEvents)
+ private val receivedTotal = AtomicInteger()
+ private val receivedBy = IntArray(nReceivers)
+
+ @Test
+ fun testSendReceiveStress() = runBlocking {
+ println("--- ChannelSendReceiveStressTest $kind with nSenders=$nSenders, nReceivers=$nReceivers")
+ val receivers = List(nReceivers) { receiverIndex ->
+ // different event receivers use different code
+ launch(Dispatchers.Default + CoroutineName("receiver$receiverIndex")) {
+ when (receiverIndex % 5) {
+ 0 -> doReceive(receiverIndex)
+ 1 -> doReceiveOrNull(receiverIndex)
+ 2 -> doIterator(receiverIndex)
+ 3 -> doReceiveSelect(receiverIndex)
+ 4 -> doReceiveSelectOrNull(receiverIndex)
+ }
+ receiversCompleted.incrementAndGet()
+ }
+ }
+ val senders = List(nSenders) { senderIndex ->
+ launch(Dispatchers.Default + CoroutineName("sender$senderIndex")) {
+ when (senderIndex % 2) {
+ 0 -> doSend(senderIndex)
+ 1 -> doSendSelect(senderIndex)
+ }
+ sendersCompleted.incrementAndGet()
+ }
+ }
+ // print progress
+ val progressJob = launch {
+ var seconds = 0
+ while (true) {
+ delay(1000)
+ println("${++seconds}: Sent ${sentTotal.get()}, received ${receivedTotal.get()}")
+ }
+ }
+ try {
+ withTimeout(timeLimit) {
+ senders.forEach { it.join() }
+ channel.close()
+ receivers.forEach { it.join() }
+ }
+ } catch (e: CancellationException) {
+ println("!!! Test timed out $e")
+ }
+ progressJob.cancel()
+ println("Tested $kind with nSenders=$nSenders, nReceivers=$nReceivers")
+ println("Completed successfully ${sendersCompleted.get()} sender coroutines")
+ println("Completed successfully ${receiversCompleted.get()} receiver coroutines")
+ println(" Sent ${sentTotal.get()} events")
+ println(" Received ${receivedTotal.get()} events")
+ println(" Received dupes ${dupes.get()}")
+ repeat(nReceivers) { receiveIndex ->
+ println(" Received by #$receiveIndex ${receivedBy[receiveIndex]}")
+ }
+ assertEquals(nSenders, sendersCompleted.get())
+ assertEquals(nReceivers, receiversCompleted.get())
+ assertEquals(0, dupes.get())
+ assertEquals(nEvents, sentTotal.get())
+ if (!kind.isConflated) assertEquals(nEvents, receivedTotal.get())
+ repeat(nReceivers) { receiveIndex ->
+ assertTrue("Each receiver should have received something", receivedBy[receiveIndex] > 0)
+ }
+ }
+
+ private suspend fun doSent() {
+ sentTotal.incrementAndGet()
+ if (!kind.isConflated) {
+ while (sentTotal.get() > receivedTotal.get() + maxBuffer)
+ yield() // throttle fast senders to prevent OOM with LinkedListChannel
+ }
+ }
+
+ private suspend fun doSend(senderIndex: Int) {
+ for (i in senderIndex until nEvents step nSenders) {
+ channel.send(i)
+ doSent()
+ }
+ }
+
+ private suspend fun doSendSelect(senderIndex: Int) {
+ for (i in senderIndex until nEvents step nSenders) {
+ select<Unit> { channel.onSend(i) { Unit } }
+ doSent()
+ }
+ }
+
+ private fun doReceived(receiverIndex: Int, event: Int) {
+ if (!received.compareAndSet(event, 0, 1)) {
+ println("Duplicate event $event at $receiverIndex")
+ dupes.incrementAndGet()
+ }
+ receivedTotal.incrementAndGet()
+ receivedBy[receiverIndex]++
+ }
+
+ private suspend fun doReceive(receiverIndex: Int) {
+ while (true) {
+ try { doReceived(receiverIndex, channel.receive()) }
+ catch (ex: ClosedReceiveChannelException) { break }
+ }
+ }
+
+ private suspend fun doReceiveOrNull(receiverIndex: Int) {
+ while (true) {
+ doReceived(receiverIndex, channel.receiveOrNull() ?: break)
+ }
+ }
+
+ private suspend fun doIterator(receiverIndex: Int) {
+ for (event in channel) {
+ doReceived(receiverIndex, event)
+ }
+ }
+
+ private suspend fun doReceiveSelect(receiverIndex: Int) {
+ while (true) {
+ try {
+ val event = select<Int> { channel.onReceive { it } }
+ doReceived(receiverIndex, event)
+ } catch (ex: ClosedReceiveChannelException) { break }
+ }
+ }
+
+ private suspend fun doReceiveSelectOrNull(receiverIndex: Int) {
+ while (true) {
+ val event = select<Int?> { channel.onReceiveOrNull { it } } ?: break
+ doReceived(receiverIndex, event)
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelsConsumeTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelsConsumeTest.kt
new file mode 100644
index 00000000..d9ef22b1
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelsConsumeTest.kt
@@ -0,0 +1,908 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+/**
+ * Tests that various operators on channels properly consume (close) their source channels.
+ */
+class ChannelsConsumeTest : TestBase() {
+ private val sourceList = (1..10).toList()
+
+ // test source with numbers 1..10
+ private fun CoroutineScope.testSource() = produce(NonCancellable) {
+ for (i in sourceList) {
+ send(i)
+ }
+ }
+
+ @Test
+ fun testConsume() {
+ checkTerminal {
+ consume {
+ assertEquals(1, receive())
+ }
+ }
+ }
+
+ @Test
+ fun testConsumeEach() {
+ checkTerminal {
+ var sum = 0
+ consumeEach { sum += it }
+ assertEquals(55, sum)
+ }
+ }
+
+ @Test
+ fun testConsumeEachIndexed() {
+ checkTerminal {
+ var sum = 0
+ consumeEachIndexed { (index, i) -> sum += index * i }
+ assertEquals(330, sum)
+ }
+ }
+
+ @Test
+ fun testElementAt() {
+ checkTerminal {
+ assertEquals(2, elementAt(1))
+ }
+ checkTerminal(expected = { it is IndexOutOfBoundsException }) {
+ elementAt(10)
+ }
+ }
+
+ @Test
+ fun testElementAtOrElse() {
+ checkTerminal {
+ assertEquals(3, elementAtOrElse(2) { error("Cannot happen") })
+ }
+ checkTerminal {
+ assertEquals(-23, elementAtOrElse(10) { -23 })
+ }
+ }
+
+ @Test
+ fun testElementOrNull() {
+ checkTerminal {
+ assertEquals(4, elementAtOrNull(3))
+ }
+ checkTerminal {
+ assertEquals(null, elementAtOrNull(10))
+ }
+ }
+
+ @Test
+ fun testFind() {
+ checkTerminal {
+ assertEquals(3, find { it % 3 == 0 })
+ }
+ }
+
+ @Test
+ fun testFindLast() {
+ checkTerminal {
+ assertEquals(9, findLast { it % 3 == 0 })
+ }
+ }
+
+ @Test
+ fun testFirst() {
+ checkTerminal {
+ assertEquals(1, first())
+ }
+ }
+
+ @Test
+ fun testFirstPredicate() {
+ checkTerminal {
+ assertEquals(3, first { it % 3 == 0 })
+ }
+ checkTerminal(expected = { it is NoSuchElementException }) {
+ first { it > 10 }
+ }
+ }
+
+ @Test
+ fun testFirstOrNull() {
+ checkTerminal {
+ assertEquals(1, firstOrNull())
+ }
+ }
+
+ @Test
+ fun testFirstOrNullPredicate() {
+ checkTerminal {
+ assertEquals(3, firstOrNull { it % 3 == 0 })
+ }
+ checkTerminal {
+ assertEquals(null, firstOrNull { it > 10 })
+ }
+ }
+
+ @Test
+ fun testIndexOf() {
+ checkTerminal {
+ assertEquals(2, indexOf(3))
+ }
+ checkTerminal {
+ assertEquals(-1, indexOf(11))
+ }
+ }
+
+ @Test
+ fun testIndexOfFirst() {
+ checkTerminal {
+ assertEquals(2, indexOfFirst { it % 3 == 0 })
+ }
+ checkTerminal {
+ assertEquals(-1, indexOfFirst { it > 10 })
+ }
+ }
+
+ @Test
+ fun testIndexOfLast() {
+ checkTerminal {
+ assertEquals(8, indexOfLast { it % 3 == 0 })
+ }
+ checkTerminal {
+ assertEquals(-1, indexOfLast { it > 10 })
+ }
+ }
+
+ @Test
+ fun testLast() {
+ checkTerminal {
+ assertEquals(10, last())
+ }
+ }
+
+ @Test
+ fun testLastPredicate() {
+ checkTerminal {
+ assertEquals(9, last { it % 3 == 0 })
+ }
+ checkTerminal(expected = { it is NoSuchElementException }) {
+ last { it > 10 }
+ }
+ }
+
+ @Test
+ fun testLastIndexOf() {
+ checkTerminal {
+ assertEquals(8, lastIndexOf(9))
+ }
+ }
+
+ @Test
+ fun testLastOrNull() {
+ checkTerminal {
+ assertEquals(10, lastOrNull())
+ }
+ }
+
+ @Test
+ fun testLastOrNullPredicate() {
+ checkTerminal {
+ assertEquals(9, lastOrNull { it % 3 == 0 })
+ }
+ checkTerminal {
+ assertEquals(null, lastOrNull { it > 10 })
+ }
+ }
+
+ @Test
+ fun testSingle() {
+ checkTerminal(expected = { it is IllegalArgumentException }) {
+ single()
+ }
+ }
+
+ @Test
+ fun testSinglePredicate() {
+ checkTerminal {
+ assertEquals(7, single { it % 7 == 0 })
+ }
+ checkTerminal(expected = { it is IllegalArgumentException }) {
+ single { it % 3 == 0 }
+ }
+ checkTerminal(expected = { it is NoSuchElementException }) {
+ single { it > 10 }
+ }
+ }
+
+ @Test
+ fun testSingleOrNull() {
+ checkTerminal {
+ assertEquals(null, singleOrNull())
+ }
+ }
+
+ @Test
+ fun testSingleOrNullPredicate() {
+ checkTerminal {
+ assertEquals(7, singleOrNull { it % 7 == 0 })
+ }
+ checkTerminal {
+ assertEquals(null, singleOrNull { it % 3 == 0 })
+ }
+ checkTerminal {
+ assertEquals(null, singleOrNull { it > 10 })
+ }
+ }
+
+ @Test
+ fun testDrop() {
+ checkTransform(sourceList.drop(3)) {
+ drop(3)
+ }
+ }
+
+ @Test
+ fun testDropWhile() {
+ checkTransform(sourceList.dropWhile { it < 4}) {
+ dropWhile { it < 4 }
+ }
+ }
+
+ @Test
+ fun testFilter() {
+ checkTransform(sourceList.filter { it % 2 == 0 }) {
+ filter { it % 2 == 0 }
+ }
+ }
+
+ @Test
+ fun testFilterIndexed() {
+ checkTransform(sourceList.filterIndexed { index, _ -> index % 2 == 0 }) {
+ filterIndexed { index, _ -> index % 2 == 0 }
+ }
+ }
+
+ @Test
+ fun testFilterIndexedToCollection() {
+ checkTerminal {
+ val list = mutableListOf<Int>()
+ filterIndexedTo(list) { index, _ -> index % 2 == 0 }
+ assertEquals(listOf(1, 3, 5, 7, 9), list)
+ }
+ }
+
+ @Test
+ fun testFilterIndexedToChannel() {
+ checkTerminal {
+ val channel = Channel<Int>()
+ val result = GlobalScope.async { channel.toList() }
+ filterIndexedTo(channel) { index, _ -> index % 2 == 0 }
+ channel.close()
+ assertEquals(listOf(1, 3, 5, 7, 9), result.await())
+ }
+ }
+
+ @Test
+ fun testFilterNot() {
+ checkTransform(sourceList.filterNot { it % 2 == 0 }) {
+ filterNot { it % 2 == 0 }
+ }
+ }
+
+ @Test
+ fun testFilterNotNullToCollection() {
+ checkTerminal {
+ val list = mutableListOf<Int>()
+ filterNotNullTo(list)
+ assertEquals((1..10).toList(), list)
+ }
+ }
+
+ @Test
+ fun testFilterNotNullToChannel() {
+ checkTerminal {
+ val channel = Channel<Int>()
+ val result = GlobalScope.async { channel.toList() }
+ filterNotNullTo(channel)
+ channel.close()
+ assertEquals((1..10).toList(), result.await())
+ }
+ }
+
+ @Test
+ fun testFilterNotToCollection() {
+ checkTerminal {
+ val list = mutableListOf<Int>()
+ filterNotTo(list) { it % 2 == 0 }
+ assertEquals(listOf(1, 3, 5, 7, 9), list)
+ }
+ }
+
+ @Test
+ fun testFilterNotToChannel() {
+ checkTerminal {
+ val channel = Channel<Int>()
+ val result = GlobalScope.async { channel.toList() }
+ filterNotTo(channel) { it % 2 == 0 }
+ channel.close()
+ assertEquals(listOf(1, 3, 5, 7, 9), result.await())
+ }
+ }
+
+ @Test
+ fun testFilterToCollection() {
+ checkTerminal {
+ val list = mutableListOf<Int>()
+ filterTo(list) { it % 2 == 0 }
+ assertEquals(listOf(2, 4, 6, 8, 10), list)
+ }
+ }
+
+ @Test
+ fun testFilterToChannel() {
+ checkTerminal {
+ val channel = Channel<Int>()
+ val result = GlobalScope.async { channel.toList() }
+ filterTo(channel) { it % 2 == 0 }
+ channel.close()
+ assertEquals(listOf(2, 4, 6, 8, 10), result.await())
+ }
+ }
+
+ @Test
+ fun testTake() {
+ checkTransform(sourceList.take(3)) {
+ take(3)
+ }
+ }
+
+ @Test
+ fun testTakeWhile() {
+ checkTransform(sourceList.takeWhile { it < 4 }) {
+ takeWhile { it < 4 }
+ }
+ }
+
+ @Test
+ fun testAssociate() {
+ checkTerminal {
+ assertEquals(sourceList.associate { it to it.toString() }, associate { it to it.toString() })
+ }
+ }
+
+ @Test
+ fun testAssociateBy() {
+ checkTerminal {
+ assertEquals(sourceList.associateBy { it.toString() }, associateBy { it.toString() })
+ }
+ }
+
+ @Test
+ fun testAssociateByTwo() {
+ checkTerminal {
+ assertEquals(sourceList.associateBy({ it.toString() }, { it + 1}), associateBy({ it.toString() }, { it + 1}))
+ }
+ }
+
+ @Test
+ fun testAssociateByToMap() {
+ checkTerminal {
+ val map = mutableMapOf<String, Int>()
+ associateByTo(map) { it.toString() }
+ assertEquals(sourceList.associateBy { it.toString() }, map)
+ }
+ }
+
+ @Test
+ fun testAssociateByTwoToMap() {
+ checkTerminal {
+ val map = mutableMapOf<String, Int>()
+ associateByTo(map, { it.toString() }, { it + 1})
+ assertEquals(sourceList.associateBy({ it.toString() }, { it + 1}), map)
+ }
+ }
+
+ @Test
+ fun testAssociateToMap() {
+ checkTerminal {
+ val map = mutableMapOf<Int, String>()
+ associateTo(map) { it to it.toString() }
+ assertEquals(sourceList.associate { it to it.toString() }, map)
+ }
+ }
+
+ @Test
+ fun testToChannel() {
+ checkTerminal {
+ val channel = Channel<Int>()
+ val result = GlobalScope.async { channel.toList() }
+ toChannel(channel)
+ channel.close()
+ assertEquals(sourceList, result.await())
+ }
+ }
+
+ @Test
+ fun testToCollection() {
+ checkTerminal {
+ val list = mutableListOf<Int>()
+ toCollection(list)
+ assertEquals(sourceList, list)
+ }
+ }
+
+ @Test
+ fun testToList() {
+ checkTerminal {
+ val list = toList()
+ assertEquals(sourceList, list)
+ }
+ }
+
+ @Test
+ fun testToMap() {
+ checkTerminal {
+ val map = map { it to it.toString() }.toMap()
+ assertEquals(sourceList.map { it to it.toString() }.toMap(), map)
+ }
+ }
+
+ @Test
+ fun testToMapWithMap() {
+ checkTerminal {
+ val map = mutableMapOf<Int, String>()
+ map { it to it.toString() }.toMap(map)
+ assertEquals(sourceList.map { it to it.toString() }.toMap(), map)
+ }
+ }
+
+ @Test
+ fun testToMutableList() {
+ checkTerminal {
+ val list = toMutableList()
+ assertEquals(sourceList, list)
+ }
+ }
+
+ @Test
+ fun testToSet() {
+ checkTerminal {
+ val set = toSet()
+ assertEquals(sourceList.toSet(), set)
+ }
+ }
+
+ @Test
+ fun testFlatMap() {
+ checkTransform(sourceList.flatMap { listOf("A$it", "B$it") }) {
+ flatMap {
+ GlobalScope.produce(coroutineContext) {
+ send("A$it")
+ send("B$it")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testGroupBy() {
+ checkTerminal {
+ val map = groupBy { it % 2 }
+ assertEquals(sourceList.groupBy { it % 2 }, map)
+ }
+ }
+
+ @Test
+ fun testGroupByTwo() {
+ checkTerminal {
+ val map = groupBy({ it % 2 }, { it.toString() })
+ assertEquals(sourceList.groupBy({ it % 2 }, { it.toString() }), map)
+ }
+ }
+
+ @Test
+ fun testGroupByTo() {
+ checkTerminal {
+ val map = mutableMapOf<Int, MutableList<Int>>()
+ groupByTo(map) { it % 2 }
+ assertEquals(sourceList.groupBy { it % 2 }, map)
+ }
+ }
+
+ @Test
+ fun testGroupByToTwo() {
+ checkTerminal {
+ val map = mutableMapOf<Int, MutableList<String>>()
+ groupByTo(map, { it % 2 }, { it.toString() })
+ assertEquals(sourceList.groupBy({ it % 2 }, { it.toString() }), map)
+ }
+ }
+
+ @Test
+ fun testMap() {
+ checkTransform(sourceList.map { it.toString() }) {
+ map { it.toString() }
+ }
+ }
+
+ @Test
+ fun testMapIndexed() {
+ checkTransform(sourceList.mapIndexed { index, v -> "$index$v" }) {
+ mapIndexed { index, v -> "$index$v" }
+ }
+ }
+
+ @Test
+ fun testMapIndexedNotNull() {
+ checkTransform(sourceList.mapIndexedNotNull { index, v -> "$index$v".takeIf { v % 2 == 0 } }) {
+ mapIndexedNotNull { index, v -> "$index$v".takeIf { v % 2 == 0 } }
+ }
+ }
+
+ @Test
+ fun testMapIndexedNotNullToCollection() {
+ checkTerminal {
+ val list = mutableListOf<String>()
+ mapIndexedNotNullTo(list) { index, v -> "$index$v".takeIf { v % 2 == 0 } }
+ assertEquals(sourceList.mapIndexedNotNull { index, v -> "$index$v".takeIf { v % 2 == 0 } }, list)
+ }
+ }
+
+ @Test
+ fun testMapIndexedNotNullToChannel() {
+ checkTerminal {
+ val channel = Channel<String>()
+ val result = GlobalScope.async { channel.toList() }
+ mapIndexedNotNullTo(channel) { index, v -> "$index$v".takeIf { v % 2 == 0 } }
+ channel.close()
+ assertEquals(sourceList.mapIndexedNotNull { index, v -> "$index$v".takeIf { v % 2 == 0 } }, result.await())
+ }
+ }
+
+ @Test
+ fun testMapIndexedToCollection() {
+ checkTerminal {
+ val list = mutableListOf<String>()
+ mapIndexedTo(list) { index, v -> "$index$v" }
+ assertEquals(sourceList.mapIndexed { index, v -> "$index$v" }, list)
+ }
+ }
+
+ @Test
+ fun testMapIndexedToChannel() {
+ checkTerminal {
+ val channel = Channel<String>()
+ val result = GlobalScope.async { channel.toList() }
+ mapIndexedTo(channel) { index, v -> "$index$v" }
+ channel.close()
+ assertEquals(sourceList.mapIndexed { index, v -> "$index$v" }, result.await())
+ }
+ }
+
+ @Test
+ fun testMapNotNull() {
+ checkTransform(sourceList.mapNotNull { (it + 3).takeIf { it % 2 == 0 } }) {
+ mapNotNull { (it + 3).takeIf { it % 2 == 0 } }
+ }
+ }
+
+ @Test
+ fun testMapNotNullToCollection() {
+ checkTerminal {
+ val list = mutableListOf<Int>()
+ mapNotNullTo(list) { (it + 3).takeIf { it % 2 == 0 } }
+ assertEquals(sourceList.mapNotNull { (it + 3).takeIf { it % 2 == 0 } }, list)
+ }
+ }
+
+ @Test
+ fun testMapNotNullToChannel() {
+ checkTerminal {
+ val channel = Channel<Int>()
+ val result = GlobalScope.async { channel.toList() }
+ mapNotNullTo(channel) { (it + 3).takeIf { it % 2 == 0 } }
+ channel.close()
+ assertEquals(sourceList.mapNotNull { (it + 3).takeIf { it % 2 == 0 } }, result.await())
+ }
+ }
+
+ @Test
+ fun testMapToCollection() {
+ checkTerminal {
+ val list = mutableListOf<Int>()
+ mapTo(list) { it + 3 }
+ assertEquals(sourceList.map { it + 3 }, list)
+ }
+ }
+
+ @Test
+ fun testMapToChannel() {
+ checkTerminal {
+ val channel = Channel<Int>()
+ val result = GlobalScope.async { channel.toList() }
+ mapTo(channel) { it + 3 }
+ channel.close()
+ assertEquals(sourceList.map { it + 3 }, result.await())
+ }
+ }
+
+ @Test
+ fun testWithIndex() {
+ checkTransform(sourceList.asSequence().withIndex().toList()) {
+ withIndex()
+ }
+ }
+
+ @Test
+ fun testDistinctBy() {
+ checkTransform(sourceList.distinctBy { it / 2 }) {
+ distinctBy { it / 2 }
+ }
+ }
+
+ @Test
+ fun testToMutableSet() {
+ checkTerminal {
+ val set = toMutableSet()
+ assertEquals(sourceList.toSet(), set)
+ }
+ }
+
+ @Test
+ fun testAll() {
+ checkTerminal {
+ val all = all { it < 11 }
+ assertEquals(sourceList.all { it < 11 }, all)
+ }
+ }
+
+ @Test
+ fun testAny() {
+ checkTerminal {
+ val any = any()
+ assertEquals(sourceList.any(), any)
+ }
+ }
+
+ @Test
+ fun testAnyPredicate() {
+ checkTerminal {
+ val any = any { it % 3 == 0 }
+ assertEquals(sourceList.any { it % 3 == 0 }, any)
+ }
+ }
+
+ @Test
+ fun testCount() {
+ checkTerminal {
+ val c = count()
+ assertEquals(sourceList.count(), c)
+ }
+ }
+
+ @Test
+ fun testCountPredicate() {
+ checkTerminal {
+ val c = count { it % 3 == 0 }
+ assertEquals(sourceList.count { it % 3 == 0 }, c)
+ }
+ }
+
+ @Test
+ fun testFold() {
+ checkTerminal {
+ val c = fold(1) { a, b -> a + b }
+ assertEquals(sourceList.fold(1) { a, b -> a + b }, c)
+ }
+ }
+
+ @Test
+ fun testFoldIndexed() {
+ checkTerminal {
+ val c = foldIndexed(1) { i, a, b -> i * a + b }
+ assertEquals(sourceList.foldIndexed(1) { i, a, b -> i * a + b }, c)
+ }
+ }
+
+ @Test
+ fun testMaxBy() {
+ checkTerminal {
+ val c = maxBy { it % 3 }
+ assertEquals(sourceList.maxBy { it % 3 }, c)
+ }
+ }
+
+ @Test
+ fun testMaxWith() {
+ checkTerminal {
+ val c = maxWith(compareBy { it % 3 })
+ assertEquals(sourceList.maxWith(compareBy { it % 3 }), c)
+ }
+ }
+
+ @Test
+ fun testMinBy() {
+ checkTerminal {
+ val c = maxBy { it % 3 }
+ assertEquals(sourceList.maxBy { it % 3 }, c)
+ }
+ }
+
+ @Test
+ fun testMinWith() {
+ checkTerminal {
+ val c = maxWith(compareBy { it % 3 })
+ assertEquals(sourceList.maxWith(compareBy { it % 3 }), c)
+ }
+ }
+
+ @Test
+ fun testNone() {
+ checkTerminal {
+ val none = none()
+ assertEquals(sourceList.none(), none)
+ }
+ }
+
+ @Test
+ fun testNonePredicate() {
+ checkTerminal {
+ val none = none { it > 10 }
+ assertEquals(sourceList.none { it > 10 }, none)
+ }
+ }
+
+ @Test
+ fun testReduce() {
+ checkTerminal {
+ val c = reduce { a, b -> a + b }
+ assertEquals(sourceList.reduce { a, b -> a + b }, c)
+ }
+ }
+
+ @Test
+ fun testReduceIndexed() {
+ checkTerminal {
+ val c = reduceIndexed { i, a, b -> i * a + b }
+ assertEquals(sourceList.reduceIndexed { i, a, b -> i * a + b }, c)
+ }
+ }
+
+ @Test
+ fun testSubBy() {
+ checkTerminal {
+ val c = sumBy { it }
+ assertEquals(sourceList.sumBy { it }, c)
+ }
+ }
+
+ @Test
+ fun testSubByDouble() {
+ checkTerminal {
+ val c = sumByDouble { it.toDouble() }
+ assertEquals(sourceList.sumByDouble { it.toDouble() }, c)
+ }
+ }
+
+ @Test
+ fun testPartition() {
+ checkTerminal {
+ val pair = partition { it % 2 == 0 }
+ assertEquals(sourceList.partition { it % 2 == 0 }, pair)
+ }
+ }
+
+ @Test
+ fun testZip() {
+ val expect = sourceList.zip(sourceList) { a, b -> a + 2 * b }
+ checkTransform(expect) {
+ with(CoroutineScope(coroutineContext)) {
+ zip(testSource()) { a, b -> a + 2*b }
+ }
+ }
+ checkTransform(expect) {
+ with(CoroutineScope(coroutineContext)) {
+ testSource().zip(this@checkTransform) { a, b -> a + 2*b }
+ }
+ }
+ }
+
+ // ------------------
+
+ private fun checkTerminal(
+ expected: ((Throwable?) -> Unit)? = null,
+ terminal: suspend ReceiveChannel<Int>.() -> Unit
+ ) {
+ checkTerminalCompletion(expected, terminal)
+ checkTerminalCancellation(expected, terminal)
+ }
+
+ private fun checkTerminalCompletion(
+ expected: ((Throwable?) -> Unit)? = null,
+ terminal: suspend ReceiveChannel<Int>.() -> Unit
+ ) {
+ val src = runBlocking {
+ val src = testSource()
+ try {
+ // terminal operation
+ terminal(src)
+ // source must be cancelled at the end of terminal op
+ if (expected != null) error("Exception was expected")
+ } catch (e: Throwable) {
+ if (expected == null) throw e
+ expected(e)
+ }
+ src
+ }
+ assertTrue(src.isClosedForReceive, "Source must be closed")
+ }
+
+ private fun checkTerminalCancellation(
+ expected: ((Throwable?) -> Unit)? = null,
+ terminal: suspend ReceiveChannel<Int>.() -> Unit
+ ) {
+ val src = runBlocking {
+ val src = testSource()
+ // terminal operation in a separate async context started until the first suspension
+ val d = async(NonCancellable, start = CoroutineStart.UNDISPATCHED) {
+ terminal(src)
+ }
+ // then cancel it
+ d.cancel()
+ // and try to get it's result
+ try {
+ d.await()
+ } catch (e: CancellationException) {
+ // ok -- was cancelled
+ } catch (e: Throwable) {
+ // if threw a different exception -- must be an expected one
+ if (expected == null) throw e
+ expected(e)
+ }
+ src
+ }
+ // source must be cancelled at the end of terminal op even if it was cancelled while in process
+ assertTrue(src.isClosedForReceive, "Source must be closed")
+ }
+
+ private fun <R> checkTransform(
+ expect: List<R>,
+ transform: suspend ReceiveChannel<Int>.() -> ReceiveChannel<R>
+ ) {
+ // check for varying number of received elements from the channel
+ for (nReceive in 0..expect.size) {
+ checkTransform(nReceive, expect, transform)
+ }
+ }
+
+ private fun <R> checkTransform(
+ nReceive: Int,
+ expect: List<R>,
+ transform: suspend ReceiveChannel<Int>.() -> ReceiveChannel<R>
+ ) {
+ val src = runBlocking {
+ val src = testSource()
+ // transform
+ val res = transform(src)
+ // receive nReceive elements from the result
+ repeat(nReceive) { i ->
+ assertEquals(expect[i], res.receive())
+ }
+ if (nReceive < expect.size) {
+ // then cancel
+ res.cancel()
+ } else {
+ // then check that result is closed
+ assertEquals(null, res.receiveOrNull(), "Result has unexpected values")
+ }
+ src
+ }
+ // source must be cancelled when runBlocking processes all the scheduled stuff
+ assertTrue(src.isClosedForReceive, "Source must be closed")
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt
new file mode 100644
index 00000000..7613f04d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2018 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.Test
+import kotlin.test.*
+
+class ChannelsJvmTest : TestBase() {
+
+ @Test
+ fun testBlocking() {
+ val ch = Channel<Int>()
+ val sum = GlobalScope.async {
+ ch.sumBy { it }
+ }
+ repeat(10) {
+ ch.sendBlocking(it)
+ }
+ ch.close()
+ assertEquals(45, runBlocking { sum.await() })
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
new file mode 100644
index 00000000..4d09e375
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.MatcherAssert.*
+import org.hamcrest.core.*
+import org.junit.*
+import java.util.concurrent.atomic.*
+
+class ConflatedBroadcastChannelNotifyStressTest : TestBase() {
+ private val nSenders = 2
+ private val nReceivers = 3
+ private val nEvents = 500_000 * stressTestMultiplier
+ private val timeLimit = 30_000L * stressTestMultiplier // 30 sec
+
+ private val broadcast = ConflatedBroadcastChannel<Int>()
+
+ private val sendersCompleted = AtomicInteger()
+ private val receiversCompleted = AtomicInteger()
+ private val sentTotal = AtomicInteger()
+ private val receivedTotal = AtomicInteger()
+
+ @Test
+ fun testStressNotify()= runBlocking {
+ println("--- ConflatedBroadcastChannelNotifyStressTest")
+ val senders = List(nSenders) { senderId ->
+ launch(Dispatchers.Default + CoroutineName("Sender$senderId")) {
+ repeat(nEvents) { i ->
+ if (i % nSenders == senderId) {
+ broadcast.offer(i)
+ sentTotal.incrementAndGet()
+ yield()
+ }
+ }
+ sendersCompleted.incrementAndGet()
+ }
+ }
+ val receivers = List(nReceivers) { receiverId ->
+ launch(Dispatchers.Default + CoroutineName("Receiver$receiverId")) {
+ var last = -1
+ while (isActive) {
+ val i = waitForEvent()
+ if (i > last) {
+ receivedTotal.incrementAndGet()
+ last = i
+ }
+ if (i >= nEvents) break
+ yield()
+ }
+ receiversCompleted.incrementAndGet()
+ }
+ }
+ // print progress
+ val progressJob = launch {
+ var seconds = 0
+ while (true) {
+ delay(1000)
+ println("${++seconds}: Sent ${sentTotal.get()}, received ${receivedTotal.get()}")
+ }
+ }
+ try {
+ withTimeout(timeLimit) {
+ senders.forEach { it.join() }
+ broadcast.offer(nEvents) // last event to signal receivers termination
+ receivers.forEach { it.join() }
+ }
+ } catch (e: CancellationException) {
+ println("!!! Test timed out $e")
+ }
+ progressJob.cancel()
+ println("Tested with nSenders=$nSenders, nReceivers=$nReceivers")
+ println("Completed successfully ${sendersCompleted.get()} sender coroutines")
+ println("Completed successfully ${receiversCompleted.get()} receiver coroutines")
+ println(" Sent ${sentTotal.get()} events")
+ println(" Received ${receivedTotal.get()} events")
+ assertThat(sendersCompleted.get(), IsEqual(nSenders))
+ assertThat(receiversCompleted.get(), IsEqual(nReceivers))
+ assertThat(sentTotal.get(), IsEqual(nEvents))
+ }
+
+ private suspend fun waitForEvent(): Int =
+ with(broadcast.openSubscription()) {
+ val value = receive()
+ 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
new file mode 100644
index 00000000..316b3785
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016-2018 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.*
+
+class ConflatedChannelCloseStressTest : TestBase() {
+
+ private val nSenders = 2
+ private val testSeconds = 3 * stressTestMultiplier
+
+ private val curChannel = AtomicReference<Channel<Int>>(Channel(Channel.CONFLATED))
+ private val sent = AtomicInteger()
+ private val closed = AtomicInteger()
+ val received = AtomicInteger()
+
+ val pool = newFixedThreadPoolContext(nSenders + 2, "TestStressClose")
+
+ @After
+ fun tearDown() {
+ pool.close()
+ }
+
+ @Test
+ fun testStressClose() = runBlocking {
+ println("--- ConflatedChannelCloseStressTest with nSenders=$nSenders")
+ val senderJobs = List(nSenders) { Job() }
+ val senders = List(nSenders) { senderId ->
+ launch(pool) {
+ var x = senderId
+ try {
+ while (isActive) {
+ try {
+ curChannel.get().offer(x)
+ x += nSenders
+ sent.incrementAndGet()
+ } catch (e: ClosedSendChannelException) {
+ // ignore
+ }
+ }
+ } finally {
+ senderJobs[senderId].cancel()
+ }
+ }
+ }
+ val closerJob = Job()
+ val closer = launch(pool) {
+ try {
+ while (isActive) {
+ flipChannel()
+ closed.incrementAndGet()
+ yield()
+ }
+ } finally {
+ closerJob.cancel()
+ }
+ }
+ val receiver = async(pool + NonCancellable) {
+ while (isActive) {
+ curChannel.get().receiveOrNull()
+ received.incrementAndGet()
+ }
+ }
+ // print stats while running
+ repeat(testSeconds) {
+ delay(1000)
+ printStats()
+ }
+ println("Stopping")
+ senders.forEach { it.cancel() }
+ closer.cancel()
+ // wait them to complete
+ println("waiting for senders...")
+ senderJobs.forEach { it.join() }
+ println("waiting for closer...")
+ closerJob.join()
+ // close cur channel
+ println("Closing channel and signalling receiver...")
+ flipChannel()
+ curChannel.get().close(StopException())
+ /// wait for receiver do complete
+ println("Waiting for receiver...")
+ try {
+ receiver.await()
+ error("Receiver should not complete normally")
+ } catch (e: StopException) {
+ // ok
+ }
+ // print stats
+ println("--- done")
+ printStats()
+ }
+
+ private fun flipChannel() {
+ val oldChannel = curChannel.get()
+ val newChannel = Channel<Int>(Channel.CONFLATED)
+ curChannel.set(newChannel)
+ check(oldChannel.close())
+ }
+
+ private fun printStats() {
+ println("sent ${sent.get()}, closed ${closed.get()}, received ${received.get()}")
+ }
+
+ class StopException : Exception()
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/channels/DoubleChannelCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/DoubleChannelCloseStressTest.kt
new file mode 100644
index 00000000..01cace72
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/DoubleChannelCloseStressTest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2018 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.*
+
+class DoubleChannelCloseStressTest : TestBase() {
+ private val nTimes = 1000 * stressTestMultiplier
+
+ @Test
+ fun testDoubleCloseStress() {
+ repeat(nTimes) {
+ val actor = GlobalScope.actor<Int>(CoroutineName("actor"), start = CoroutineStart.LAZY) {
+ // empty -- just closes channel
+ }
+ GlobalScope.launch(CoroutineName("sender")) {
+ try {
+ actor.send(1)
+ } catch (e: ClosedSendChannelException) {
+ // ok -- closed before send
+ }
+ }
+ Thread.sleep(1)
+ actor.close()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt
new file mode 100644
index 00000000..864a0b4c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.channels.*
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class InvokeOnCloseStressTest : TestBase(), CoroutineScope {
+
+ private val iterations = 1000 * stressTestMultiplier
+
+ private val pool = newFixedThreadPoolContext(3, "InvokeOnCloseStressTest")
+ override val coroutineContext: CoroutineContext
+ get() = pool
+
+ @After
+ fun tearDown() {
+ pool.close()
+ }
+
+ @Test
+ fun testInvokedExactlyOnce() = runBlocking {
+ runStressTest(TestChannelKind.ARRAY_1)
+ }
+
+ @Test
+ fun testInvokedExactlyOnceBroadcast() = runBlocking {
+ runStressTest(TestChannelKind.CONFLATED_BROADCAST)
+ }
+
+ private suspend fun runStressTest(kind: TestChannelKind) {
+ repeat(iterations) {
+ val counter = AtomicInteger(0)
+ val channel = kind.create()
+
+ val latch = CountDownLatch(1)
+ val j1 = async {
+ latch.await()
+ channel.close()
+ }
+
+ val j2 = async {
+ latch.await()
+ channel.invokeOnClose { counter.incrementAndGet() }
+ }
+
+ val j3 = async {
+ latch.await()
+ channel.invokeOnClose { counter.incrementAndGet() }
+ }
+
+ latch.countDown()
+ joinAll(j1, j2, j3)
+ assertEquals(1, counter.get())
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ProduceConsumeJvmTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ProduceConsumeJvmTest.kt
new file mode 100644
index 00000000..1ae62d81
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/ProduceConsumeJvmTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-2018 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.*
+import org.junit.Assert.*
+import org.junit.runner.*
+import org.junit.runners.*
+
+@RunWith(Parameterized::class)
+class ProduceConsumeJvmTest(
+ private val capacity: Int,
+ private val number: Int
+) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "capacity={0}, number={1}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> =
+ listOf(0, 1, 10, 1000, Channel.UNLIMITED).flatMap { capacity ->
+ listOf(1, 10, 1000).map { number ->
+ arrayOf<Any>(capacity, number)
+ }
+ }
+ }
+
+ @Test
+ fun testProducer() = runTest {
+ var sentAll = false
+ val producer = produce(capacity = capacity) {
+ for (i in 1..number) {
+ send(i)
+ }
+ sentAll = true
+ }
+ var consumed = 0
+ for (x in producer) {
+ consumed++
+ }
+ assertTrue(sentAll)
+ assertEquals(number, consumed)
+ }
+
+ @Test
+ fun testActor() = runTest {
+ val received = CompletableDeferred<Int>()
+ val actor = actor<Int>(capacity = capacity) {
+ var n = 0
+ for(i in channel) {
+ n++
+ }
+ received.complete(n)
+ }
+ for(i in 1..number) {
+ actor.send(i)
+ }
+ actor.close()
+ assertEquals(number, received.await())
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt
new file mode 100644
index 00000000..a0541754
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2018 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.*
+
+class RandevouzChannelStressTest : TestBase() {
+
+ @Test
+ fun testStress() = runTest {
+ val n = 100_000 * stressTestMultiplier
+ val q = Channel<Int>(Channel.RENDEZVOUS)
+ val sender = launch {
+ for (i in 1..n) q.send(i)
+ expect(2)
+ }
+ val receiver = launch {
+ for (i in 1..n) check(q.receive() == i)
+ expect(3)
+ }
+ expect(1)
+ sender.join()
+ receiver.join()
+ finish(4)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/SendReceiveJvmStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/SendReceiveJvmStressTest.kt
new file mode 100644
index 00000000..60d1adba
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/SendReceiveJvmStressTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016-2018 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.runner.*
+import org.junit.runners.*
+import kotlin.test.*
+
+@RunWith(Parameterized::class)
+class SendReceiveJvmStressTest(private val channel: Channel<Int>) : TestBase() {
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = listOf(
+ Channel<Int>(1),
+ Channel (10),
+ Channel(1_000_000),
+ Channel(Channel.UNLIMITED),
+ Channel(Channel.RENDEZVOUS)
+ ).map { arrayOf<Any>(it) }
+ }
+
+ @Test
+ fun testStress() = runTest {
+ val n = 100_000 * stressTestMultiplier
+ val sender = launch {
+ for (i in 1..n) {
+ channel.send(i)
+ }
+ expect(2)
+ }
+ val receiver = launch {
+ for (i in 1..n) {
+ val next = channel.receive()
+ check(next == i)
+ }
+ expect(3)
+ }
+ expect(1)
+ sender.join()
+ receiver.join()
+ finish(4)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/SimpleSendReceiveJvmTest.kt b/kotlinx-coroutines-core/jvm/test/channels/SimpleSendReceiveJvmTest.kt
new file mode 100644
index 00000000..54023a3e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/SimpleSendReceiveJvmTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+import org.junit.runner.*
+import org.junit.runners.*
+import kotlin.coroutines.*
+
+@RunWith(Parameterized::class)
+class SimpleSendReceiveJvmTest(
+ private val kind: TestChannelKind,
+ val n: Int,
+ val concurrent: Boolean
+) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "{0}, n={1}, concurrent={2}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = TestChannelKind.values().flatMap { kind ->
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1000).flatMap { n ->
+ listOf(false, true).map { concurrent ->
+ arrayOf<Any>(kind, n, concurrent)
+ }
+ }
+ }
+ }
+
+ val channel = kind.create()
+
+ @Test
+ fun testSimpleSendReceive() = runBlocking {
+ val ctx = if (concurrent) Dispatchers.Default else coroutineContext
+ launch(ctx) {
+ repeat(n) { channel.send(it) }
+ channel.close()
+ }
+ var expected = 0
+ for (x in channel) {
+ if (!kind.isConflated) {
+ assertThat(x, IsEqual(expected++))
+ } else {
+ assertTrue(x >= expected)
+ expected = x + 1
+ }
+ }
+ if (!kind.isConflated) {
+ assertThat(expected, IsEqual(n))
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt
new file mode 100644
index 00000000..51789078
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.selects.*
+import org.junit.Test
+import org.junit.runner.*
+import org.junit.runners.*
+import kotlin.test.*
+
+@RunWith(Parameterized::class)
+class TickerChannelCommonTest(private val channelFactory: Channel) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> =
+ Channel.values().map { arrayOf<Any>(it) }
+ }
+
+ enum class Channel {
+ FIXED_PERIOD {
+ override fun invoke(delay: Long, initialDelay: Long) =
+ ticker(delay, initialDelayMillis = initialDelay, mode = TickerMode.FIXED_PERIOD)
+ },
+
+ FIXED_DELAY {
+ override fun invoke(delay: Long, initialDelay: Long) =
+ ticker(delay, initialDelayMillis = initialDelay, mode = TickerMode.FIXED_DELAY)
+ };
+
+ abstract operator fun invoke(delay: Long, initialDelay: Long = 0): ReceiveChannel<Unit>
+ }
+
+ @Test
+ fun testDelay() = withVirtualTimeSource {
+ runTest {
+ val delayChannel = channelFactory(delay = 10000)
+ delayChannel.checkNotEmpty()
+ delayChannel.checkEmpty()
+
+ delay(5000)
+ delayChannel.checkEmpty()
+ delay(5100)
+ delayChannel.checkNotEmpty()
+
+ delayChannel.cancel()
+ delay(5100)
+ assertFailsWith<CancellationException> { delayChannel.poll() }
+ }
+ }
+
+ @Test
+ fun testInitialDelay() = withVirtualTimeSource {
+ runTest {
+ val delayChannel = channelFactory(initialDelay = 750, delay = 1000)
+ delayChannel.checkEmpty()
+ delay(500)
+ delayChannel.checkEmpty()
+ delay(300)
+ delayChannel.checkNotEmpty()
+
+ // Regular delay
+ delay(750)
+ delayChannel.checkEmpty()
+ delay(260)
+ delayChannel.checkNotEmpty()
+ delayChannel.cancel()
+ }
+ }
+
+ @Test
+ fun testReceive() = withVirtualTimeSource {
+ runTest {
+ val delayChannel = channelFactory(delay = 1000)
+ delayChannel.checkNotEmpty()
+ var value = withTimeoutOrNull(750) {
+ delayChannel.receive()
+ 1
+ }
+
+ assertNull(value)
+ value = withTimeoutOrNull(260) {
+ delayChannel.receive()
+ 1
+ }
+
+ assertNotNull(value)
+ delayChannel.cancel()
+ }
+ }
+
+ @Test
+ fun testComplexOperator() = withVirtualTimeSource {
+ runTest {
+ val producer = GlobalScope.produce {
+ for (i in 1..7) {
+ send(i)
+ delay(1000)
+ }
+ }
+
+ val averages = producer.averageInTimeWindow(3000).toList()
+ assertEquals(listOf(2.0, 5.0, 7.0), averages)
+ }
+ }
+
+ private fun ReceiveChannel<Int>.averageInTimeWindow(timespan: Long) = GlobalScope.produce {
+ val delayChannel = channelFactory(delay = timespan, initialDelay = timespan)
+ var sum = 0
+ var n = 0
+ whileSelect {
+ this@averageInTimeWindow.onReceiveOrClosed {
+ if (it.isClosed) {
+ // Send leftovers and bail out
+ if (n != 0) send(sum / n.toDouble())
+ false
+ } else {
+ sum += it.value
+ ++n
+ true
+ }
+ }
+
+ // Timeout, send aggregated average and reset counters
+ delayChannel.onReceive {
+ send(sum / n.toDouble())
+ sum = 0
+ n = 0
+ true
+ }
+ }
+
+ delayChannel.cancel()
+ }
+
+ @Test
+ fun testStress() = runTest {
+ // No OOM/SOE
+ val iterations = 100_000 * stressTestMultiplier
+ val delayChannel = channelFactory(0)
+ repeat(iterations) {
+ delayChannel.receive()
+ }
+
+ delayChannel.cancel()
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testNegativeDelay() {
+ channelFactory(-1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testNegativeInitialDelay() {
+ channelFactory(initialDelay = -1, delay = 100)
+ }
+}
+
+fun ReceiveChannel<Unit>.checkEmpty() = assertNull(poll())
+
+fun ReceiveChannel<Unit>.checkNotEmpty() {
+ assertNotNull(poll())
+ assertNull(poll())
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/TickerChannelTest.kt b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelTest.kt
new file mode 100644
index 00000000..c421bd33
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-2018 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.*
+
+class TickerChannelTest : TestBase() {
+ @Test
+ fun testFixedDelayChannelBackpressure() = withVirtualTimeSource {
+ runTest {
+ val delayChannel = ticker(delayMillis = 1000, initialDelayMillis = 0, mode = TickerMode.FIXED_DELAY)
+ delayChannel.checkNotEmpty()
+ delayChannel.checkEmpty()
+
+ delay(1500)
+ delayChannel.checkNotEmpty()
+ delay(500)
+ delayChannel.checkEmpty()
+ delay(520)
+ delayChannel.checkNotEmpty()
+ delayChannel.cancel()
+ }
+ }
+
+ @Test
+ fun testDelayChannelBackpressure() = withVirtualTimeSource {
+ runTest {
+ val delayChannel = ticker(delayMillis = 1000, initialDelayMillis = 0)
+ delayChannel.checkNotEmpty()
+ delayChannel.checkEmpty()
+
+ delay(1500)
+ delayChannel.checkNotEmpty()
+ delay(520)
+ delayChannel.checkNotEmpty()
+ delay(500)
+ delayChannel.checkEmpty()
+ delay(520)
+ delayChannel.checkNotEmpty()
+ delayChannel.cancel()
+ }
+ }
+
+ @Test
+ fun testDelayChannelBackpressure2() = withVirtualTimeSource {
+ runTest {
+ val delayChannel = ticker(delayMillis = 1000, initialDelayMillis = 0)
+ delayChannel.checkNotEmpty()
+ delayChannel.checkEmpty()
+
+ delay(2500)
+ delayChannel.checkNotEmpty()
+ delay(510)
+ delayChannel.checkNotEmpty()
+ delay(510)
+ delayChannel.checkEmpty()
+ delay(510)
+ delayChannel.checkNotEmpty()
+ delayChannel.cancel()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt
new file mode 100644
index 00000000..cea9713f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class CoroutineExceptionHandlerJvmTest : TestBase() {
+
+ 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 testFailingHandler() = runBlocking {
+ expect(1)
+ val job = GlobalScope.launch(CoroutineExceptionHandler { _, _ -> throw AssertionError() }) {
+ expect(2)
+ throw TestException()
+ }
+
+ job.join()
+ assertTrue(caughtException is RuntimeException)
+ assertTrue(caughtException.cause is AssertionError)
+ assertTrue(caughtException.suppressed[0] is TestException)
+
+ finish(3)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt b/kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt
new file mode 100644
index 00000000..13023e31
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import java.io.*
+import java.util.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+/**
+ * Proxy for [Throwable.getSuppressed] for tests, which are compiled for both JDK 1.6 and JDK 1.8,
+ * but run only under JDK 1.8
+ */
+@Suppress("ConflictingExtensionProperty")
+val Throwable.suppressed: Array<Throwable> get() {
+ val method = this::class.java.getMethod("getSuppressed") ?: error("This test can only be run using JDK 1.7")
+ @Suppress("UNCHECKED_CAST")
+ return method.invoke(this) as Array<Throwable>
+}
+
+internal inline fun <reified T : Throwable> checkException(exception: Throwable): Boolean {
+ assertTrue(exception is T)
+ assertTrue(exception.suppressed.isEmpty())
+ assertNull(exception.cause)
+ return true
+}
+
+internal fun checkCycles(t: Throwable) {
+ val sw = StringWriter()
+ t.printStackTrace(PrintWriter(sw))
+ assertFalse(sw.toString().contains("CIRCULAR REFERENCE"))
+}
+
+class CapturingHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
+ CoroutineExceptionHandler
+{
+ private var unhandled: ArrayList<Throwable>? = ArrayList()
+
+ override fun handleException(context: CoroutineContext, exception: Throwable) = synchronized<Unit>(this) {
+ unhandled!!.add(exception)
+ }
+
+ fun getExceptions(): List<Throwable> = synchronized(this) {
+ return unhandled!!.also { unhandled = null }
+ }
+
+ fun getException(): Throwable = synchronized(this) {
+ val size = unhandled!!.size
+ assert(size == 1) { "Expected one unhandled exception, but have $size: $unhandled" }
+ return unhandled!![0].also { unhandled = null }
+ }
+}
+
+internal fun captureExceptionsRun(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
+): Throwable {
+ val handler = CapturingHandler()
+ runBlocking(context + handler, block = block)
+ return handler.getException()
+}
+
+internal fun captureMultipleExceptionsRun(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
+): List<Throwable> {
+ val handler = CapturingHandler()
+ runBlocking(context + handler, block = block)
+ return handler.getExceptions()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/JobBasicCancellationTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/JobBasicCancellationTest.kt
new file mode 100644
index 00000000..28d85fe3
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/JobBasicCancellationTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import java.io.*
+import kotlin.test.*
+
+/*
+ * Basic checks that check that cancellation more or less works,
+ * parent is not cancelled on child cancellation and launch {}, Job(), async {} and
+ * CompletableDeferred behave properly
+ */
+
+@Suppress("DEPRECATION") // cancel(cause)
+class JobBasicCancellationTest : TestBase() {
+
+ @Test
+ fun testJobCancelChild() = runTest {
+ val parent = launch {
+ expect(1)
+ val child = launch {
+ expect(2)
+ }
+
+ yield()
+ expect(3)
+ child.cancel()
+ child.join()
+ expect(4)
+ }
+
+ parent.join()
+ finish(5)
+ }
+
+ @Test
+ fun testJobCancelChildAtomic() = runTest {
+ val parent = launch {
+ expect(1)
+ val child = launch(start = CoroutineStart.ATOMIC) {
+ expect(3)
+ }
+
+ expect(2)
+ child.cancel()
+ child.join()
+ yield()
+ expect(4)
+ }
+
+ parent.join()
+ assertTrue(parent.isCompleted)
+ assertFalse(parent.isCancelled)
+ finish(5)
+ }
+
+ @Test
+ fun testAsyncCancelChild() = runTest {
+ val parent = async {
+ expect(1)
+ val child = async {
+ expect(2)
+ }
+
+ yield()
+ expect(3)
+ child.cancel()
+ child.await()
+ expect(4)
+ }
+
+ parent.await()
+ finish(5)
+ }
+
+ @Test
+ fun testAsyncCancelChildAtomic() = runTest {
+ val parent = async {
+ expect(1)
+ val child = async(start = CoroutineStart.ATOMIC) {
+ expect(3)
+ }
+
+ expect(2)
+ child.cancel()
+ child.join()
+ expect(4)
+ }
+
+ parent.await()
+ finish(5)
+ }
+
+ @Test
+ fun testNestedAsyncFailure() = runTest {
+ val deferred = async(NonCancellable) {
+ val nested = async(NonCancellable) {
+ expect(3)
+ throw IOException()
+ }
+
+ expect(2)
+ yield()
+ expect(4)
+ nested.await()
+ }
+
+ expect(1)
+ try {
+ deferred.await()
+ } catch (e: IOException) {
+ finish(5)
+ }
+ }
+
+ @Test
+ fun testCancelJobImpl() = runTest {
+ val parent = launch {
+ expect(1)
+ val child = Job(coroutineContext[Job])
+ expect(2)
+ child.cancel() // cancel without cause -- should not cancel us (parent)
+ child.join()
+ expect(3)
+ }
+ parent.join()
+ finish(4)
+ }
+
+ @Test
+ fun cancelCompletableDeferred() = runTest {
+ val parent = launch {
+ expect(1)
+ val child = CompletableDeferred<Unit>(coroutineContext[Job])
+ expect(2)
+ child.cancel() // cancel without cause -- should not cancel us (parent)
+ child.join()
+ expect(3)
+ }
+
+ parent.join()
+ finish(4)
+ }
+
+ @Test
+ fun testConsecutiveCancellation() {
+ val deferred = CompletableDeferred<Int>()
+ assertTrue(deferred.completeExceptionally(IndexOutOfBoundsException()))
+ assertFalse(deferred.completeExceptionally(AssertionError())) // second is too late
+ val cause = deferred.getCancellationException().cause!!
+ assertTrue(cause is IndexOutOfBoundsException)
+ assertNull(cause.cause)
+ assertTrue(cause.suppressed.isEmpty())
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionHandlingTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionHandlingTest.kt
new file mode 100644
index 00000000..eaa73bdb
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionHandlingTest.kt
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineStart.*
+import org.junit.Test
+import java.io.*
+import kotlin.test.*
+
+@Suppress("DEPRECATION") // cancel(cause)
+class JobExceptionHandlingTest : TestBase() {
+
+ @Test
+ fun testChildException() {
+ /*
+ * Root parent: JobImpl()
+ * Child: throws ISE
+ * Result: ISE in exception handler
+ */
+ val exception = captureExceptionsRun {
+ val job = Job()
+ launch(job, start = ATOMIC) {
+ expect(2)
+ throw IllegalStateException()
+ }
+
+ expect(1)
+ job.join()
+ finish(3)
+ }
+
+ checkException<IllegalStateException>(exception)
+ }
+
+ @Test
+ fun testAsyncCancellationWithCauseAndParent() = runTest {
+ val parent = Job()
+ val deferred = async(parent) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ expect(1)
+ yield()
+ parent.completeExceptionally(IOException())
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: CancellationException) {
+ assertTrue(e.suppressed.isEmpty())
+ assertTrue(e.cause?.suppressed?.isEmpty() ?: false)
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testAsyncCancellationWithCauseAndParentDoesNotTriggerHandling() = runTest {
+ val parent = Job()
+ val job = launch(parent) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ expect(1)
+ yield()
+ parent.completeExceptionally(IOException())
+ job.join()
+ finish(3)
+ }
+
+ @Test
+ fun testExceptionDuringCancellation() {
+ /*
+ * Root parent: JobImpl()
+ * Launcher: cancels job
+ * Child: throws ISE
+ * Result: ISE in exception handler
+ *
+ * Github issue #354
+ */
+ val exception = captureExceptionsRun {
+ val job = Job()
+ val child = launch(job, start = ATOMIC) {
+ expect(2)
+ throw IllegalStateException()
+ }
+
+ expect(1)
+ job.cancelAndJoin()
+ assert(child.isCompleted && !child.isActive)
+ finish(3)
+ }
+
+ checkException<IllegalStateException>(exception)
+ }
+
+ @Test
+ fun testExceptionOnChildCancellation() {
+ /*
+ * Root parent: JobImpl()
+ * Child: launch inner child and cancels parent
+ * Inner child: throws AE
+ * Result: AE in exception handler
+ */
+ val exception = captureExceptionsRun {
+ val job = Job()
+ launch(job) {
+ expect(2) // <- child is launched successfully
+
+ launch {
+ expect(3) // <- child's child is launched successfully
+ try {
+ yield()
+ } catch (e: CancellationException) {
+ throw ArithmeticException()
+ }
+ }
+
+ yield()
+ expect(4)
+ job.cancel()
+ }
+
+ expect(1)
+ job.join()
+ finish(5)
+ }
+
+ checkException<ArithmeticException>(exception)
+ }
+
+ @Test
+ fun testInnerChildException() {
+ /*
+ * Root parent: JobImpl()
+ * Launcher: launch child and cancel root
+ * Child: launch nested child atomically and yields
+ * Inner child: throws AE
+ * Result: AE
+ */
+ val exception = captureExceptionsRun {
+ val job = Job()
+ launch(job, start = ATOMIC) {
+ expect(2)
+ launch(start = ATOMIC) {
+ expect(3) // <- child's child is launched successfully
+ throw ArithmeticException()
+ }
+
+ yield() // will throw cancellation exception
+ }
+
+ expect(1)
+ job.cancelAndJoin()
+ finish(4)
+ }
+
+ checkException<ArithmeticException>(exception)
+ }
+
+ @Test
+ fun testExceptionOnChildCancellationWithCause() {
+ /*
+ * Root parent: JobImpl()
+ * Child: launch inner child and cancels parent with IOE
+ * Inner child: throws AE
+ * Result: IOE with suppressed AE
+ */
+ val exception = captureExceptionsRun {
+ val job = Job()
+ launch(job) {
+ expect(2) // <- child is launched successfully
+ launch {
+ expect(3) // <- child's child is launched successfully
+ try {
+ yield()
+ } catch (e: CancellationException) {
+ throw ArithmeticException()
+ }
+ }
+
+ yield()
+ expect(4)
+ job.completeExceptionally(IOException())
+ }
+
+ expect(1)
+ job.join()
+ finish(5)
+ }
+
+ assertTrue(exception is ArithmeticException)
+ assertNull(exception.cause)
+ assertTrue(exception.suppressed.isEmpty())
+ }
+
+ @Test
+ fun testMultipleChildrenThrowAtomically() {
+ /*
+ * Root parent: JobImpl()
+ * Launcher: launches child
+ * Child: launch 3 children, each of them throws an exception (AE, IOE, IAE) and calls delay()
+ * Result: AE with suppressed IOE and IAE
+ */
+ val exception = captureExceptionsRun {
+ val job = Job()
+ launch(job, start = ATOMIC) {
+ expect(2)
+ launch(start = ATOMIC) {
+ expect(3)
+ throw ArithmeticException()
+ }
+
+ launch(start = ATOMIC) {
+ expect(4)
+ throw IOException()
+ }
+
+ launch(start = ATOMIC) {
+ expect(5)
+ throw IllegalArgumentException()
+ }
+
+ delay(Long.MAX_VALUE)
+ }
+
+ expect(1)
+ job.join()
+ finish(6)
+ }
+
+ assertTrue(exception is ArithmeticException)
+ val suppressed = exception.suppressed
+ assertEquals(2, suppressed.size)
+ assertTrue(suppressed[0] is IOException)
+ assertTrue(suppressed[1] is IllegalArgumentException)
+ }
+
+ @Test
+ fun testMultipleChildrenAndParentThrowsAtomic() {
+ /*
+ * Root parent: JobImpl()
+ * Launcher: launches child
+ * Child: launch 2 children (each of them throws an exception (IOE, IAE)), throws AE
+ * Result: AE with suppressed IOE and IAE
+ */
+ val exception = captureExceptionsRun {
+ val job = Job()
+ launch(job, start = ATOMIC) {
+ expect(2)
+ launch(start = ATOMIC) {
+ expect(3)
+ throw IOException()
+ }
+
+ launch(start = ATOMIC) {
+ expect(4)
+ throw IllegalArgumentException()
+ }
+
+ throw AssertionError()
+ }
+
+ expect(1)
+ job.join()
+ finish(5)
+ }
+
+ assertTrue(exception is AssertionError)
+ val suppressed = exception.suppressed
+ assertEquals(2, suppressed.size)
+ assertTrue(suppressed[0] is IOException)
+ assertTrue(suppressed[1] is IllegalArgumentException)
+ }
+
+ @Test
+ fun testExceptionIsHandledOnce() = runTest(unhandled = listOf { e -> e is TestException }) {
+ val job = Job()
+ val j1 = launch(job) {
+ expect(1)
+ delay(Long.MAX_VALUE)
+ }
+
+ val j2 = launch(job) {
+ expect(2)
+ throw TestException()
+ }
+
+ joinAll(j1 ,j2)
+ finish(3)
+ }
+
+ @Test
+ fun testCancelledParent() = runTest {
+ expect(1)
+ val parent = Job()
+ parent.completeExceptionally(TestException())
+ launch(parent) {
+ expectUnreached()
+ }.join()
+ finish(2)
+ }
+
+ @Test
+ fun testExceptionIsNotReported() = runTest {
+ try {
+ expect(1)
+ coroutineScope {
+ val job = Job(coroutineContext[Job])
+ launch(job) {
+ throw TestException()
+ }
+ }
+ expectUnreached()
+ } catch (e: TestException) {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testExceptionIsNotReportedTripleChain() = runTest {
+ try {
+ expect(1)
+ coroutineScope {
+ val job = Job(Job(Job(coroutineContext[Job])))
+ launch(job) {
+ throw TestException()
+ }
+ }
+ expectUnreached()
+ } catch (e: TestException) {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testAttachToCancelledJob() = runTest(unhandled = listOf({ e -> e is TestException })) {
+ val parent = launch(Job()) {
+ throw TestException()
+ }.apply { join() }
+
+ launch(parent) { expectUnreached() }
+ launch(Job(parent)) { expectUnreached() }
+ }
+
+ @Test
+ fun testBadException() = runTest(unhandled = listOf({e -> e is BadException})) {
+ val job = launch(Job()) {
+ expect(2)
+ launch {
+ expect(3)
+ throw BadException()
+ }
+
+ launch(start = ATOMIC) {
+ expect(4)
+ throw BadException()
+ }
+
+ yield()
+ BadException()
+ }
+
+ expect(1)
+ yield()
+ yield()
+ expect(5)
+ job.join()
+ finish(6)
+ }
+
+ private class BadException : Exception() {
+ override fun hashCode(): Int {
+ throw AssertionError()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionsStressTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionsStressTest.kt
new file mode 100644
index 00000000..4c977e85
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionsStressTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class JobExceptionsStressTest : TestBase() {
+
+ private val executor = newFixedThreadPoolContext(5, "JobExceptionsStressTest")
+
+ @After
+ fun tearDown() {
+ executor.close()
+ }
+
+ @Test
+ fun testMultipleChildrenThrows() {
+ /*
+ * Root parent: launched job
+ * Owner: launch 3 children, every of it throws an exception, and then call delay()
+ * Result: one of the exceptions with the rest two as suppressed
+ */
+ repeat(1000 * stressTestMultiplier) {
+ val exception = captureExceptionsRun(executor) {
+ val barrier = CyclicBarrier(4)
+ val job = launch(NonCancellable) {
+ launch(start = CoroutineStart.ATOMIC) {
+ barrier.await()
+ throw TestException1()
+ }
+ launch(start = CoroutineStart.ATOMIC) {
+ barrier.await()
+ throw TestException2()
+ }
+ launch(start = CoroutineStart.ATOMIC) {
+ barrier.await()
+ throw TestException3()
+ }
+ delay(1000) // to avoid OutOfMemory errors....
+ }
+ barrier.await()
+ job.join()
+ }
+ val classes = mutableSetOf(
+ TestException1::class,
+ TestException2::class,
+ TestException3::class
+ )
+ val suppressedExceptions = exception.suppressed.toSet()
+ assertTrue(classes.remove(exception::class),
+ "Failed to remove ${exception::class} from $suppressedExceptions"
+ )
+ for (throwable in suppressedExceptions.toSet()) { // defensive copy
+ assertTrue(classes.remove(throwable::class),
+ "Failed to remove ${throwable::class} from $suppressedExceptions")
+ }
+ assertTrue(classes.isEmpty(), "Expected all exception to be present, but following exceptions are missing: $classes")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/JobNestedExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/JobNestedExceptionsTest.kt
new file mode 100644
index 00000000..4a5fa746
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/JobNestedExceptionsTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import java.io.*
+import kotlin.test.*
+
+class JobNestedExceptionsTest : TestBase() {
+
+ @Test
+ fun testExceptionUnwrapping() {
+ val exception = captureExceptionsRun {
+ val job = Job()
+ launch(job) {
+ expect(2)
+ launch {
+ launch {
+ launch {
+ throw IllegalStateException()
+ }
+ }
+ }
+ }
+
+ expect(1)
+ job.join()
+ finish(3)
+ }
+
+ checkException<IllegalStateException>(exception)
+ checkCycles(exception)
+ }
+
+ @Test
+ fun testExceptionUnwrappingWithSuspensions() {
+ val exception = captureExceptionsRun {
+ val job = Job()
+ launch(job) {
+ expect(2)
+ launch {
+ launch {
+ launch {
+ launch {
+ throw IOException()
+ }
+ yield()
+ }
+ delay(Long.MAX_VALUE)
+ }
+ delay(Long.MAX_VALUE)
+ }
+ delay(Long.MAX_VALUE)
+ }
+
+ expect(1)
+ job.join()
+ finish(3)
+ }
+
+ assertTrue(exception is IOException)
+ }
+
+ @Test
+ fun testNestedAtomicThrow() {
+ val exception = captureExceptionsRun {
+ expect(1)
+ val job = launch(NonCancellable + CoroutineName("outer"), start = CoroutineStart.ATOMIC) {
+ expect(2)
+ launch(CoroutineName("nested"), start = CoroutineStart.ATOMIC) {
+ expect(4)
+ throw IOException()
+ }
+ expect(3)
+ throw ArithmeticException()
+ }
+ job.join()
+ finish(5)
+ }
+ assertTrue(exception is ArithmeticException, "Found $exception")
+ checkException<IOException>(exception.suppressed[0])
+ }
+
+ @Test
+ fun testChildThrowsDuringCompletion() {
+ val exceptions = captureMultipleExceptionsRun {
+ expect(1)
+ val job = launch(NonCancellable + CoroutineName("outer"), start = CoroutineStart.ATOMIC) {
+ expect(2)
+ launch(CoroutineName("nested"), start = CoroutineStart.ATOMIC) {
+ expect(4)
+ launch(CoroutineName("nested2"), start = CoroutineStart.ATOMIC) {
+ // This child attaches to the parent and throws after parent completion
+ expect(6)
+ throw NullPointerException()
+ }
+ expect(5)
+ throw IOException()
+ }
+ expect(3)
+ throw ArithmeticException()
+ }
+
+ job.join()
+ finish(7)
+ }
+
+ assertEquals(1, exceptions.size, "Found $exceptions")
+ val exception = exceptions[0]
+ assertTrue(exception is ArithmeticException, "Exception is $exception")
+ val suppressed = exception.suppressed
+ val ioe = suppressed[0]
+ assertTrue(ioe is IOException)
+ checkException<NullPointerException>(ioe.suppressed[0])
+ checkCycles(exception)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/ProduceExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/ProduceExceptionsTest.kt
new file mode 100644
index 00000000..33585f44
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/ProduceExceptionsTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.junit.Test
+import kotlin.test.*
+
+class ProduceExceptionsTest : TestBase() {
+
+ @Test
+ fun testFailingProduce() = runTest(unhandled = listOf({ e -> e is TestException })) {
+ expect(1)
+ val producer = produce<Int>(Job()) {
+ expect(2)
+ try {
+ yield()
+ } finally {
+ expect(3)
+ throw TestException()
+
+ }
+ }
+
+ yield()
+ producer.cancel()
+ yield()
+ finish(4)
+ }
+
+ @Test
+ fun testSuppressedExceptionUncaught() =
+ runTest(unhandled = listOf({ e -> e is TestException && e.suppressed[0] is TestException2 })) {
+ val produce = produce<Int>(Job()) {
+ launch(start = CoroutineStart.ATOMIC) {
+ throw TestException()
+ }
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException2()
+ }
+ }
+
+ yield()
+ produce.cancel()
+ }
+
+ @Test
+ fun testSuppressedException() = runTest {
+ val produce = produce<Int>(NonCancellable) {
+ launch(start = CoroutineStart.ATOMIC) {
+ throw TestException() // child coroutine fails
+ }
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException2() // but parent throws another exception while cleaning up
+ }
+ }
+ try {
+ produce.receive()
+ expectUnreached()
+ } catch (e: TestException) {
+ assertTrue(e.suppressed[0] is TestException2)
+ }
+ }
+
+ @Test
+ fun testCancelProduceChannel() = runTest {
+ var channel: ReceiveChannel<Int>? = null
+ channel = produce {
+ expect(2)
+ channel!!.cancel()
+ try {
+ send(1)
+ } catch (e: CancellationException) {
+ expect(3)
+ throw e
+ }
+ }
+
+ expect(1)
+ yield()
+ try {
+ channel.receive()
+ } catch (e: CancellationException) {
+ assertTrue(e.suppressed.isEmpty())
+ finish(4)
+ }
+ }
+
+ @Test
+ fun testCancelProduceChannelWithException() = runTest {
+ var channel: ReceiveChannel<Int>? = null
+ channel = produce(NonCancellable) {
+ expect(2)
+ channel!!.cancel(TestCancellationException())
+ try {
+ send(1)
+ // Not a ClosedForSendException
+ } catch (e: TestCancellationException) {
+ expect(3)
+ throw e
+ }
+ }
+
+ expect(1)
+ yield()
+ try {
+ channel.receive()
+ } catch (e: TestCancellationException) {
+ assertTrue(e.suppressed.isEmpty())
+ finish(4)
+ }
+ }
+
+ @Test
+ fun testCancelChannelWithJob() = runTest {
+ val job = Job()
+ val channel = produce(job) {
+ expect(2)
+ job.cancel()
+ try {
+ send(1)
+ } catch (e: CancellationException) {
+ expect(3)
+ throw e
+ }
+ }
+
+ expect(1)
+ yield()
+ try {
+ channel.receive()
+ } catch (e: CancellationException) {
+ assertTrue(e.suppressed.isEmpty())
+ finish(4)
+ }
+ }
+
+ @Test
+ fun testCancelChannelWithJobWithException() = runTest {
+ val job = Job()
+ val channel = produce(job) {
+ expect(2)
+ job.completeExceptionally(TestException2())
+ try {
+ send(1)
+ } catch (e: CancellationException) { // Not a TestException2
+ expect(3)
+ throw e
+ }
+ }
+
+ expect(1)
+ yield()
+ try {
+ channel.receive()
+ } catch (e: CancellationException) {
+ // RECOVER_STACK_TRACES
+ assertTrue(e.cause?.cause is TestException2)
+ finish(4)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
new file mode 100644
index 00000000..70336659
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+@Suppress("UNREACHABLE_CODE", "UNUSED", "UNUSED_PARAMETER")
+class StackTraceRecoveryCustomExceptionsTest : TestBase() {
+
+ internal class NonCopyable(val customData: Int) : Throwable() {
+ // Bait
+ public constructor(cause: Throwable) : this(42)
+ }
+
+ internal class Copyable(val customData: Int) : Throwable(), CopyableThrowable<Copyable> {
+ // Bait
+ public constructor(cause: Throwable) : this(42)
+
+ override fun createCopy(): Copyable {
+ val copy = Copyable(customData)
+ copy.initCause(this)
+ return copy
+ }
+ }
+
+ @Test
+ fun testStackTraceNotRecovered() = runTest {
+ try {
+ withContext(wrapperDispatcher(coroutineContext)) {
+ throw NonCopyable(239)
+ }
+ expectUnreached()
+ } catch (e: NonCopyable) {
+ assertEquals(239, e.customData)
+ assertNull(e.cause)
+ }
+ }
+
+ @Test
+ fun testStackTraceRecovered() = runTest {
+ try {
+ withContext(wrapperDispatcher(coroutineContext)) {
+ throw Copyable(239)
+ }
+ expectUnreached()
+ } catch (e: Copyable) {
+ assertEquals(239, e.customData)
+ val cause = e.cause
+ assertTrue(cause is Copyable)
+ assertEquals(239, cause.customData)
+ }
+ }
+
+ internal class WithDefault(message: String = "default") : Exception(message)
+
+ @Test
+ fun testStackTraceRecoveredWithCustomMessage() = runTest {
+ try {
+ withContext(wrapperDispatcher(coroutineContext)) {
+ throw WithDefault("custom")
+ }
+ expectUnreached()
+ } catch (e: WithDefault) {
+ assertEquals("custom", e.message)
+ val cause = e.cause
+ assertTrue(cause is WithDefault)
+ assertEquals("custom", cause.message)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt
new file mode 100644
index 00000000..748f0c16
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DeferredResultUnused", "DEPRECATION")
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.junit.*
+import kotlin.coroutines.*
+
+class StackTraceRecoveryNestedChannelsTest : TestBase() {
+
+ private val channel = Channel<Int>(0)
+
+ private suspend fun sendWithContext(ctx: CoroutineContext) = withContext(ctx) {
+ sendInChannel()
+ yield() // TCE
+ }
+
+ private suspend fun sendInChannel() {
+ channel.send(42)
+ yield() // TCE
+ }
+
+ private suspend fun sendFromScope() = coroutineScope {
+ sendWithContext(wrapperDispatcher(coroutineContext))
+ }
+
+ @Test
+ fun testOfferWithCurrentContext() = runTest {
+ channel.close(RecoverableTestException())
+
+ try {
+ yield() // Will be fixed in 1.3.20 after KT-27190
+ sendWithContext(coroutineContext)
+ } catch (e: Exception) {
+ verifyStackTrace(e,
+ "kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferWithCurrentContext\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:34)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:180)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.send(AbstractChannel.kt:168)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest.sendInChannel(StackTraceRecoveryNestedChannelsTest.kt:24)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendWithContext\$2.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:19)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendWithContext\$2.invoke(StackTraceRecoveryNestedChannelsTest.kt)\n" +
+ "\tat kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:85)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:146)\n" +
+ "\tat kotlinx.coroutines.BuildersKt.withContext(Unknown Source)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest.sendWithContext(StackTraceRecoveryNestedChannelsTest.kt:18)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferWithCurrentContext\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:37)\n" +
+ "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferWithCurrentContext\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:34)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n")
+ }
+ }
+
+ @Test
+ fun testOfferWithContextWrapped() = runTest {
+ channel.close(RecoverableTestException())
+
+ try {
+ sendWithContext(wrapperDispatcher(coroutineContext))
+ } catch (e: Exception) {
+ verifyStackTrace(e,
+ "kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferWithContextWrapped\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:59)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:180)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.send(AbstractChannel.kt:168)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest.sendInChannel(StackTraceRecoveryNestedChannelsTest.kt:24)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendWithContext\$2.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:19)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferWithContextWrapped\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:62)\n" +
+ "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferWithContextWrapped\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:59)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)")
+ }
+ }
+
+ @Test
+ fun testOfferFromScope() = runTest {
+ channel.close(RecoverableTestException())
+
+ try {
+ sendFromScope()
+ } catch (e: Exception) {
+ verifyStackTrace(e,
+ "kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferFromScope\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:81)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:180)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.send(AbstractChannel.kt:168)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest.sendInChannel(StackTraceRecoveryNestedChannelsTest.kt:24)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendWithContext\$2.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:19)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendFromScope\$2.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:28)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferFromScope\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:84)\n" +
+ "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testOfferFromScope\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:81)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)")
+ }
+ }
+
+ // Slow path via suspending send
+ @Test
+ fun testSendFromScope() = runTest {
+ val deferred = async {
+ try {
+ expect(1)
+ sendFromScope()
+ } catch (e: Exception) {
+ verifyStackTrace(e,
+ "kotlinx.coroutines.RecoverableTestCancellationException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testSendFromScope\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:118)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:180)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.send(AbstractChannel.kt:168)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest.sendInChannel(StackTraceRecoveryNestedChannelsTest.kt:24)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendWithContext\$2.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:19)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendFromScope\$2.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:29)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testSendFromScope\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:109)\n" +
+ "Caused by: kotlinx.coroutines.RecoverableTestCancellationException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testSendFromScope\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:118)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)")
+ }
+ }
+
+ yield()
+ expect(2)
+ // Cancel is an analogue of `produce` failure, just a shorthand
+ channel.cancel(RecoverableTestCancellationException())
+ finish(3)
+ 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(e,
+ "kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@3af42ad0\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:186)\n" +
+ "\tat kotlinx.coroutines.channels.ChannelCoroutine.offer(ChannelCoroutine.kt)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testCancelledOffer\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:150)\n" +
+ "Caused by: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@3af42ad0\n",
+ // ... java.lang.* stuff and JobSupport.* snipped here ...
+ "\tat kotlinx.coroutines.Job\$DefaultImpls.cancel\$default(Job.kt:164)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testCancelledOffer\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:148)"
+ )
+ finish(2)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt
new file mode 100644
index 00000000..bea18a43
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt
@@ -0,0 +1,99 @@
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.*
+import kotlin.coroutines.*
+
+class StackTraceRecoveryNestedScopesTest : TestBase() {
+
+ private val TEST_MACROS = "TEST_NAME"
+
+ private val expectedTrace = "kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.failure(StackTraceRecoveryNestedScopesTest.kt:9)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.access\$failure(StackTraceRecoveryNestedScopesTest.kt:7)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$createFailingAsync\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:12)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$callWithTimeout\$2.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:23)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$callCoroutineScope\$2.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:29)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$$TEST_MACROS\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:36)\n" +
+ "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.failure(StackTraceRecoveryNestedScopesTest.kt:9)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.access\$failure(StackTraceRecoveryNestedScopesTest.kt:7)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$createFailingAsync\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:12)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)"
+
+ private fun failure(): String = throw RecoverableTestException()
+
+ private fun CoroutineScope.createFailingAsync() = async {
+ failure()
+ }
+
+ private suspend fun callWithContext(doYield: Boolean) = withContext(wrapperDispatcher(coroutineContext)) {
+ if (doYield) yield()
+ createFailingAsync().await()
+ yield()
+ }
+
+ private suspend fun callWithTimeout(doYield: Boolean) = withTimeout(Long.MAX_VALUE) {
+ if (doYield) yield()
+ callWithContext(doYield)
+ yield()
+ }
+
+ private suspend fun callCoroutineScope(doYield: Boolean) = coroutineScope {
+ if (doYield) yield()
+ callWithTimeout(doYield)
+ yield()
+ }
+
+ @Test
+ fun testNestedScopes() = runTest {
+ try {
+ callCoroutineScope(false)
+ } catch (e: Exception) {
+ verifyStackTrace(e, expectedTrace.replace(TEST_MACROS, "testNestedScopes"))
+ }
+ }
+
+ @Test
+ fun testNestedScopesYield() = runTest {
+ try {
+ callCoroutineScope(true)
+ } catch (e: Exception) {
+ verifyStackTrace(e, expectedTrace.replace(TEST_MACROS, "testNestedScopesYield"))
+ }
+ }
+
+ @Test
+ fun testAwaitNestedScopes() = runTest {
+ val deferred = async(NonCancellable) {
+ callCoroutineScope(false)
+ }
+
+ verifyAwait(deferred)
+ }
+
+ private suspend fun verifyAwait(deferred: Deferred<Unit>) {
+ try {
+ deferred.await()
+ } catch (e: Exception) {
+ verifyStackTrace(e,
+ "kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.failure(StackTraceRecoveryNestedScopesTest.kt:23)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.access\$failure(StackTraceRecoveryNestedScopesTest.kt:7)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$createFailingAsync\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:26)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\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" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.failure(StackTraceRecoveryNestedScopesTest.kt:23)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.access\$failure(StackTraceRecoveryNestedScopesTest.kt:7)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$createFailingAsync\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:26)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt
new file mode 100644
index 00000000..5073b7fd
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DeferredResultUnused")
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class StackTraceRecoveryNestedTest : TestBase() {
+
+ @Test
+ fun testNestedAsync() = runTest {
+ val rootAsync = async(NonCancellable) {
+ expect(1)
+
+ // Just a noise for unwrapping
+ async {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ // Do not catch, fail on cancellation
+ async {
+ expect(3)
+ async {
+ expect(4)
+ delay(Long.MAX_VALUE)
+ }
+
+ async {
+ expect(5)
+ // 1) await(), catch, verify and rethrow
+ try {
+ val nested = async {
+ expect(6)
+ throw RecoverableTestException()
+ }
+
+ nested.awaitNested()
+ } catch (e: RecoverableTestException) {
+ expect(7)
+ e.verifyException(
+ "await\$suspendImpl",
+ "awaitNested",
+ "\$testNestedAsync\$1\$rootAsync\$1\$2\$2.invokeSuspend"
+ )
+ // Just rethrow it
+ throw e
+ }
+ }
+ }
+ }
+
+ try {
+ rootAsync.awaitRootLevel()
+ } catch (e: RecoverableTestException) {
+ e.verifyException("await\$suspendImpl", "awaitRootLevel")
+ finish(8)
+ }
+ }
+
+ private suspend fun Deferred<*>.awaitRootLevel() {
+ await()
+ assertTrue(true)
+ }
+
+ private suspend fun Deferred<*>.awaitNested() {
+ await()
+ assertTrue(true)
+ }
+
+ private fun RecoverableTestException.verifyException(vararg expectedTraceElements: String) {
+ // It is "recovered" only once
+ assertEquals(1, depth())
+ val stacktrace = stackTrace.map { it.methodName }.toSet()
+ assertTrue(expectedTraceElements.all { stacktrace.contains(it) })
+ }
+
+ private fun Throwable.depth(): Int {
+ val cause = cause ?: return 0
+ return 1 + cause.depth()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt
new file mode 100644
index 00000000..e7b46cd1
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.selects.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.concurrent.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+/*
+ * All stacktrace validation skips line numbers
+ */
+class StackTraceRecoveryTest : TestBase() {
+
+ @Test
+ fun testAsync() = runTest {
+ fun createDeferred(depth: Int): Deferred<*> {
+ return if (depth == 0) {
+ async<Unit>(coroutineContext + NonCancellable) {
+ throw ExecutionException(null)
+ }
+ } else {
+ createDeferred(depth - 1)
+ }
+ }
+
+ 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" +
+ "\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 kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n"
+ )
+ nestedMethod(deferred, *traces.toTypedArray())
+ deferred.join()
+ }
+
+ @Test
+ fun testCompletedAsync() = runTest {
+ val deferred = async<Unit>(coroutineContext + NonCancellable) {
+ throw ExecutionException(null)
+ }
+
+ deferred.join()
+ val stacktrace = listOf(
+ "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)",
+ "Caused by: java.util.concurrent.ExecutionException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:44)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)"
+ )
+ nestedMethod(deferred, *stacktrace.toTypedArray())
+ }
+
+ private suspend fun nestedMethod(deferred: Deferred<*>, vararg traces: String) {
+ oneMoreNestedMethod(deferred, *traces)
+ assertTrue(true) // Prevent tail-call optimization
+ }
+
+ private suspend fun oneMoreNestedMethod(deferred: Deferred<*>, vararg traces: String) {
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: ExecutionException) {
+ verifyStackTrace(e, *traces)
+ }
+ }
+
+ @Test
+ fun testReceiveFromChannel() = runTest {
+ val channel = Channel<Int>()
+ val job = launch {
+ expect(2)
+ channel.close(IllegalArgumentException())
+ }
+
+ expect(1)
+ channelNestedMethod(
+ channel,
+ "java.lang.IllegalArgumentException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testReceiveFromChannel\$1\$job\$1.invokeSuspend(StackTraceRecoveryTest.kt:93)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.channelNestedMethod(StackTraceRecoveryTest.kt:110)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testReceiveFromChannel\$1.invokeSuspend(StackTraceRecoveryTest.kt:89)",
+ "Caused by: java.lang.IllegalArgumentException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testReceiveFromChannel\$1\$job\$1.invokeSuspend(StackTraceRecoveryTest.kt:93)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n" +
+ "\tat kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:152)")
+ expect(3)
+ job.join()
+ finish(4)
+ }
+
+ @Test
+ fun testReceiveFromClosedChannel() = runTest {
+ val channel = Channel<Int>()
+ channel.close(IllegalArgumentException())
+ channelNestedMethod(
+ channel,
+ "java.lang.IllegalArgumentException\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractChannel.receiveResult(AbstractChannel.kt:574)\n" +
+ "\tat kotlinx.coroutines.channels.AbstractChannel.receive(AbstractChannel.kt:567)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.channelNestedMethod(StackTraceRecoveryTest.kt:117)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testReceiveFromClosedChannel\$1.invokeSuspend(StackTraceRecoveryTest.kt:111)\n",
+ "Caused by: java.lang.IllegalArgumentException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testReceiveFromClosedChannel\$1.invokeSuspend(StackTraceRecoveryTest.kt:110)")
+ }
+
+ private suspend fun channelNestedMethod(channel: Channel<Int>, vararg traces: String) {
+ try {
+ channel.receive()
+ expectUnreached()
+ } catch (e: IllegalArgumentException) {
+ verifyStackTrace(e, *traces)
+ }
+ }
+
+ @Test
+ fun testWithContext() = runTest {
+ val deferred = async<Unit>(NonCancellable, start = CoroutineStart.LAZY) {
+ throw RecoverableTestException()
+ }
+
+ outerMethod(deferred,
+ "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" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testWithContext\$1.invokeSuspend(StackTraceRecoveryTest.kt:141)\n",
+ "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testWithContext\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n")
+ deferred.join()
+ }
+
+ private suspend fun outerMethod(deferred: Deferred<*>, vararg traces: String) {
+ withContext(Dispatchers.IO) {
+ innerMethod(deferred, *traces)
+ }
+
+ assertTrue(true)
+ }
+
+ private suspend fun innerMethod(deferred: Deferred<*>, vararg traces: String) {
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: RecoverableTestException) {
+ verifyStackTrace(e, *traces)
+ }
+ }
+
+ @Test
+ fun testCoroutineScope() = runTest {
+ val deferred = async<Unit>(NonCancellable, start = CoroutineStart.LAZY) {
+ throw RecoverableTestException()
+ }
+
+ outerScopedMethod(deferred,
+ "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" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1.invokeSuspend(StackTraceRecoveryTest.kt:141)\n",
+ "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n")
+ deferred.join()
+ }
+
+ public class TrickyException() : Throwable() {
+ // To be sure ctor is never invoked
+ @Suppress("UNUSED", "UNUSED_PARAMETER")
+ private constructor(message: String, cause: Throwable): this() {
+ error("Should never be called")
+ }
+
+ override fun initCause(cause: Throwable?): Throwable {
+ error("Can't call initCause")
+ }
+ }
+
+ @Test
+ fun testThrowingInitCause() = runTest {
+ val deferred = async<Unit>(NonCancellable) {
+ expect(2)
+ throw TrickyException()
+ }
+
+ try {
+ expect(1)
+ deferred.await()
+ } catch (e: TrickyException) {
+ assertNull(e.cause)
+ finish(3)
+ }
+ }
+
+ private suspend fun outerScopedMethod(deferred: Deferred<*>, vararg traces: String) = coroutineScope {
+ supervisorScope {
+ innerMethod(deferred, *traces)
+ assertTrue(true)
+ }
+ assertTrue(true)
+ }
+
+ @Test
+ fun testSelect() = runTest {
+ expect(1)
+ val result = runCatching { doSelect() }
+ expect(3)
+ verifyStackTrace(result.exceptionOrNull()!!,
+ "kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$doSelect\$\$inlined\$select\$lambda\$1.invokeSuspend(StackTraceRecoveryTest.kt:211)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testSelect\$1.invokeSuspend(StackTraceRecoveryTest.kt:199)\n" +
+ "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$doSelect\$\$inlined\$select\$lambda\$1.invokeSuspend(StackTraceRecoveryTest.kt:211)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)")
+ finish(4)
+ }
+
+ private suspend fun doSelect(): Int {
+ val job = CompletableDeferred(Unit)
+ return select {
+ job.onJoin {
+ yield()
+ expect(2)
+ throw RecoverableTestException()
+ }
+ }
+ }
+
+ @Test
+ fun testSelfSuppression() = runTest {
+ try {
+ runBlocking {
+ val job = launch {
+ coroutineScope<Unit> {
+ throw RecoverableTestException()
+ }
+ }
+
+ job.join()
+ expectUnreached()
+ }
+ expectUnreached()
+ } catch (e: RecoverableTestException) {
+ checkCycles(e)
+ }
+ }
+
+
+ private suspend fun throws() {
+ yield() // TCE
+ throw RecoverableTestException()
+ }
+
+ private suspend fun awaiter() {
+ val task = GlobalScope.async(Dispatchers.Default, start = CoroutineStart.LAZY) { throws() }
+ task.await()
+ yield() // TCE
+ }
+
+ @Test
+ fun testNonDispatchedRecovery() {
+ val await = suspend { awaiter() }
+
+ val barrier = CyclicBarrier(2)
+ var exception: Throwable? = null
+
+ thread {
+ await.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) {
+ exception = it.exceptionOrNull()
+ barrier.await()
+ })
+ }
+
+ barrier.await()
+ 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")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt b/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt
new file mode 100644
index 00000000..15884332
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt
@@ -0,0 +1,50 @@
+package kotlinx.coroutines.exceptions
+
+import java.io.*
+import kotlin.test.*
+
+public fun verifyStackTrace(e: Throwable, vararg traces: String) {
+ val stacktrace = toStackTrace(e)
+ val normalizedActual = stacktrace.normalizeStackTrace()
+ traces.forEach {
+ val normalizedExpected = it.normalizeStackTrace()
+ if (!normalizedActual.contains(normalizedExpected)) {
+ // A more readable error message would be produced by assertEquals
+ assertEquals(normalizedExpected, normalizedActual, "Actual trace does not contain expected one")
+ }
+ }
+ // Check "Caused by" counts
+ val causes = stacktrace.count("Caused by")
+ assertNotEquals(0, causes)
+ assertEquals(traces.map { it.count("Caused by") }.sum(), causes)
+}
+
+public fun toStackTrace(t: Throwable): String {
+ val sw = StringWriter() as Writer
+ t.printStackTrace(PrintWriter(sw))
+ return sw.toString()
+}
+
+public fun String.normalizeStackTrace(): String =
+ applyBackspace()
+ .replace(Regex(":[0-9]+"), "") // remove line numbers
+ .replace("kotlinx_coroutines_core_main", "") // yay source sets
+ .replace("kotlinx_coroutines_core", "")
+ .replace(Regex("@[0-9a-f]+"), "") // remove hex addresses in debug toStrings
+ .lines().joinToString("\n") // normalize line separators
+
+public fun String.applyBackspace(): String {
+ val array = toCharArray()
+ val stack = CharArray(array.size)
+ var stackSize = -1
+ for (c in array) {
+ if (c != '\b') {
+ stack[++stackSize] = c
+ } else {
+ --stackSize
+ }
+ }
+ return String(stack, 0, stackSize)
+}
+
+public fun String.count(substring: String): Int = split(substring).size - 1 \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt b/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt
new file mode 100644
index 00000000..6034fccb
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import java.io.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+@Suppress("DEPRECATION")
+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) {
+ override fun onStart() {
+ expect(3)
+ }
+
+ override fun onCancelling(cause: Throwable?) {
+ assertTrue(cause is ArithmeticException)
+ assertTrue(cause.suppressed.isEmpty())
+ expect(5)
+ }
+
+ override fun onCompleted(value: String) {
+ expectUnreached()
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ assertTrue(cause is ArithmeticException)
+ checkException<IOException>(cause.suppressed[0])
+ expect(8)
+ }
+ }
+
+ coroutine.invokeOnCompletion(onCancelling = true) {
+ assertTrue(it is ArithmeticException)
+ assertTrue(it.suppressed.isEmpty())
+ expect(6)
+ }
+
+ coroutine.invokeOnCompletion {
+ assertTrue(it is ArithmeticException)
+ checkException<IOException>(it.suppressed[0])
+ expect(9)
+ }
+
+ expect(2)
+ coroutine.start()
+ expect(4)
+ coroutine.cancelInternal(ArithmeticException())
+ expect(7)
+ coroutine.resumeWithException(IOException())
+ finish(10)
+ }
+
+ @Test
+ fun testExceptionUnwrapping() = runTest {
+ val channel = Channel<Int>()
+
+ val deferred = async(NonCancellable) {
+ launch {
+ while (true) channel.send(1)
+ }
+
+ launch {
+ val exception = RecoverableTestCancellationException()
+ channel.cancel(exception)
+ throw exception
+ }
+ }
+
+ try {
+ deferred.await()
+ } catch (e: RecoverableTestException) {
+ assertTrue(e.suppressed.isEmpty())
+ assertTrue(e.cause!!.suppressed.isEmpty())
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt
new file mode 100644
index 00000000..29954663
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class WithContextCancellationStressTest : TestBase() {
+
+ private val iterations = 15_000 * stressTestMultiplier
+ private val pool = newFixedThreadPoolContext(3, "WithContextCancellationStressTest")
+
+ @After
+ fun tearDown() {
+ pool.close()
+ }
+
+ @Test
+ @Suppress("DEPRECATION")
+ fun testConcurrentFailure() = runBlocking {
+ var eCnt = 0
+ var e1Cnt = 0
+ var e2Cnt = 0
+
+ repeat(iterations) {
+ val barrier = CyclicBarrier(4)
+ val ctx = pool + NonCancellable
+ var e1 = false
+ var e2 = false
+ val jobWithContext = async(ctx) {
+ withContext(wrapperDispatcher(coroutineContext)) {
+ launch {
+ barrier.await()
+ e1 = true
+ throw TestException1()
+ }
+
+ launch {
+ barrier.await()
+ e2 = true
+ throw TestException2()
+ }
+
+ barrier.await()
+ throw TestException()
+ }
+ }
+
+ barrier.await()
+
+ try {
+ jobWithContext.await()
+ } catch (e: Throwable) {
+ when (e) {
+ is TestException -> {
+ eCnt++
+ e.checkSuppressed(e1 = e1, e2 = e2)
+ }
+ is TestException1 -> {
+ e1Cnt++
+ e.checkSuppressed(ex = true, e2 = e2)
+ }
+ is TestException2 -> {
+ e2Cnt++
+ e.checkSuppressed(ex = true, e1 = e1)
+ }
+ else -> error("Unexpected exception $e")
+ }
+ }
+ }
+
+ require(eCnt > 0) { "At least one TestException expected" }
+ require(e1Cnt > 0) { "At least one TestException1 expected" }
+ require(e2Cnt > 0) { "At least one TestException2 expected" }
+ }
+
+ private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
+ val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
+ return object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ dispatcher.dispatch(context, block)
+ }
+ }
+ }
+
+ private fun Throwable.checkSuppressed(
+ ex: Boolean = false,
+ e1: Boolean = false,
+ e2: Boolean = false
+ ) {
+ val suppressed: Array<Throwable> = suppressed
+ if (ex) {
+ assertTrue(suppressed.any { it is TestException }, "TestException should be present: $this")
+ }
+ if (e1) {
+ assertTrue(suppressed.any { it is TestException1 }, "TestException1 should be present: $this")
+ }
+ if (e2) {
+ assertTrue(suppressed.any { it is TestException2 }, "TestException2 should be present: $this")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/WithContextExceptionHandlingTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextExceptionHandlingTest.kt
new file mode 100644
index 00000000..a95a3da3
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextExceptionHandlingTest.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import org.junit.runner.*
+import org.junit.runners.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+@RunWith(Parameterized::class)
+class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() {
+ enum class Mode { WITH_CONTEXT, ASYNC_AWAIT }
+
+ companion object {
+ @Parameterized.Parameters(name = "mode={0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = Mode.values().map { arrayOf<Any>(it) }
+ }
+
+ @Test
+ fun testCancellation() = runTest {
+ /*
+ * context cancelled without cause
+ * code itself throws TE2
+ * Result: TE2
+ */
+ runCancellation(null, TestException2()) { e ->
+ assertTrue(e is TestException2)
+ assertNull(e.cause)
+ val suppressed = e.suppressed
+ assertTrue(suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testCancellationWithException() = runTest {
+ /*
+ * context cancelled with TCE
+ * block itself throws TE2
+ * Result: TE (CancellationException is always ignored)
+ */
+ val cancellationCause = TestCancellationException()
+ runCancellation(cancellationCause, TestException2()) { e ->
+ assertTrue(e is TestException2)
+ assertNull(e.cause)
+ val suppressed = e.suppressed
+ assertTrue(suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testSameException() = runTest {
+ /*
+ * context cancelled with TCE
+ * block itself throws the same TCE
+ * Result: TCE
+ */
+ val cancellationCause = TestCancellationException()
+ runCancellation(cancellationCause, cancellationCause) { e ->
+ assertTrue(e is TestCancellationException)
+ assertNull(e.cause)
+ val suppressed = e.suppressed
+ assertTrue(suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testSameCancellation() = runTest {
+ /*
+ * context cancelled with TestCancellationException
+ * block itself throws the same TCE
+ * Result: TCE
+ */
+ val cancellationCause = TestCancellationException()
+ runCancellation(cancellationCause, cancellationCause) { e ->
+ assertSame(e, cancellationCause)
+ assertNull(e.cause)
+ val suppressed = e.suppressed
+ assertTrue(suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testSameCancellationWithException() = runTest {
+ /*
+ * context cancelled with CancellationException(TE)
+ * block itself throws the same TE
+ * Result: TE
+ */
+ val cancellationCause = CancellationException()
+ val exception = TestException()
+ cancellationCause.initCause(exception)
+ runCancellation(cancellationCause, exception) { e ->
+ assertSame(exception, e)
+ assertNull(e.cause)
+ assertTrue(e.suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testConflictingCancellation() = runTest {
+ /*
+ * context cancelled with TCE
+ * block itself throws CE(TE)
+ * Result: TE (because cancellation exception is always ignored and not handled)
+ */
+ val cancellationCause = TestCancellationException()
+ val thrown = CancellationException()
+ thrown.initCause(TestException())
+ runCancellation(cancellationCause, thrown) { e ->
+ assertSame(cancellationCause, e)
+ assertTrue(e.suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testConflictingCancellation2() = runTest {
+ /*
+ * context cancelled with TE
+ * block itself throws CE
+ * Result: TE
+ */
+ val cancellationCause = TestCancellationException()
+ val thrown = CancellationException()
+ runCancellation(cancellationCause, thrown) { e ->
+ assertSame(cancellationCause, e)
+ val suppressed = e.suppressed
+ assertTrue(suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testConflictingCancellation3() = runTest {
+ /*
+ * context cancelled with TCE
+ * block itself throws TCE
+ * Result: TCE
+ */
+ val cancellationCause = TestCancellationException()
+ val thrown = TestCancellationException()
+ runCancellation(cancellationCause, thrown) { e ->
+ assertSame(cancellationCause, e)
+ assertNull(e.cause)
+ assertTrue(e.suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testThrowingCancellation() = runTest {
+ val thrown = TestCancellationException()
+ runThrowing(thrown) { e ->
+ assertSame(thrown, e)
+ }
+ }
+
+ @Test
+ fun testThrowingCancellationWithCause() = runTest {
+ // Exception are never unwrapped, so if CE(TE) is thrown then it is the cancellation cause
+ val thrown = TestCancellationException()
+ thrown.initCause(TestException())
+ runThrowing(thrown) { e ->
+ assertSame(thrown, e)
+ }
+ }
+
+ @Test
+ fun testCancel() = runTest {
+ runOnlyCancellation(null) { e ->
+ val cause = e.cause as JobCancellationException // shall be recovered JCE
+ assertNull(cause.cause)
+ assertTrue(e.suppressed.isEmpty())
+ assertTrue(cause.suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testCancelWithCause() = runTest {
+ val cause = TestCancellationException()
+ runOnlyCancellation(cause) { e ->
+ assertSame(cause, e)
+ assertTrue(e.suppressed.isEmpty())
+ }
+ }
+
+ @Test
+ fun testCancelWithCancellationException() = runTest {
+ val cause = TestCancellationException()
+ runThrowing(cause) { e ->
+ assertSame(cause, e)
+ assertNull(e.cause)
+ assertTrue(e.suppressed.isEmpty())
+ }
+ }
+
+ private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
+ val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
+ return object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ dispatcher.dispatch(context, block)
+ }
+ }
+ }
+
+ private suspend fun runCancellation(
+ cancellationCause: CancellationException?,
+ thrownException: Throwable,
+ exceptionChecker: (Throwable) -> Unit
+ ) {
+ expect(1)
+
+ try {
+ withCtx(wrapperDispatcher(coroutineContext)) { job ->
+ require(isActive) // not cancelled yet
+ job.cancel(cancellationCause)
+ require(!isActive) // now cancelled
+ expect(2)
+ throw thrownException
+ }
+ } catch (e: Throwable) {
+ exceptionChecker(e)
+ finish(3)
+ return
+ }
+ fail()
+ }
+
+ private suspend fun runThrowing(
+ thrownException: Throwable,
+ exceptionChecker: (Throwable) -> Unit
+ ) {
+ expect(1)
+ try {
+ withCtx(wrapperDispatcher(coroutineContext).minusKey(Job)) {
+ require(isActive)
+ expect(2)
+ throw thrownException
+ }
+ } catch (e: Throwable) {
+ exceptionChecker(e)
+ finish(3)
+ return
+ }
+ fail()
+ }
+
+ private suspend fun withCtx(context: CoroutineContext, job: Job = Job(), block: suspend CoroutineScope.(Job) -> Nothing) {
+ when (mode) {
+ Mode.WITH_CONTEXT -> withContext(context + job) {
+ block(job)
+ }
+ Mode.ASYNC_AWAIT -> CoroutineScope(coroutineContext).async(context + job) {
+ block(job)
+ }.await()
+ }
+ }
+
+ private suspend fun runOnlyCancellation(
+ cancellationCause: CancellationException?,
+ exceptionChecker: (Throwable) -> Unit
+ ) {
+ expect(1)
+ val job = Job()
+ try {
+ withContext(wrapperDispatcher(coroutineContext) + job) {
+ require(isActive) // still active
+ job.cancel(cancellationCause)
+ require(!isActive) // is already cancelled
+ expect(2)
+ }
+ } catch (e: Throwable) {
+ exceptionChecker(e)
+ finish(3)
+ return
+ }
+ fail()
+ }
+} \ 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
new file mode 100644
index 00000000..f7104034
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2016-2019 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 org.junit.Test
+import kotlin.concurrent.*
+import kotlin.test.*
+
+class CallbackFlowTest : TestBase() {
+
+ private class CallbackApi(val block: (SendChannel<Int>) -> Unit) {
+ var started = false
+ @Volatile
+ var stopped = false
+ lateinit var thread: Thread
+
+ fun start(sink: SendChannel<Int>) {
+ started = true
+ thread = thread {
+ while (!stopped) {
+ block(sink)
+ }
+ }
+ }
+
+ fun stop() {
+ stopped = true
+ }
+ }
+
+ @Test(timeout = 5_000L)
+ fun testThrowingConsumer() = runTest {
+ var i = 0
+ val api = CallbackApi {
+ runCatching { it.offer(++i) }
+ }
+
+ val flow = channelFlow<Int> {
+ api.start(channel)
+ awaitClose {
+ api.stop()
+ }
+ }
+
+ var receivedConsensus = 0
+ var isDone = false
+ var exception: Throwable? = null
+ val job = flow
+ .filter { it > 10 }
+ .launchIn(this) {
+ onEach {
+ if (it == 11) {
+ ++receivedConsensus
+ } else {
+ receivedConsensus = 42
+ }
+ throw RuntimeException()
+ }
+ catch<Throwable> { exception = it }
+ finally { isDone = true }
+ }
+ job.join()
+ assertEquals(1, receivedConsensus)
+ assertTrue(isDone)
+ assertTrue { exception is RuntimeException }
+ api.thread.join()
+ assertTrue(api.started)
+ assertTrue(api.stopped)
+ }
+
+ @Test(timeout = 5_000L)
+ fun testThrowingSource() = runBlocking {
+ var i = 0
+ val api = CallbackApi {
+ if (i < 5) {
+ it.offer(++i)
+ } else {
+ it.close(RuntimeException())
+ }
+ }
+
+ val flow = callbackFlow<Int>() {
+ api.start(channel)
+ awaitClose {
+ api.stop()
+ }
+ }
+
+ var received = 0
+ var isDone = false
+ var exception: Throwable? = null
+ val job = flow.launchIn(this) {
+ onEach { ++received }
+ catch<Throwable> { exception = it }
+ finally { isDone = true }
+ }
+
+ job.join()
+ assertTrue(isDone)
+ assertTrue { exception is RuntimeException }
+ api.thread.join()
+ assertTrue(api.started)
+ assertTrue(api.stopped)
+ }
+
+
+ @Test
+ fun testMergeExample() = runTest {
+ // Too slow on JS
+ withContext(Dispatchers.Default) {
+ val f1 = (1..10_000).asFlow()
+ val f2 = (10_001..20_000).asFlow()
+ assertEquals((1..20_000).toSet(), f1.merge(f2).toSet())
+ }
+ }
+
+ private fun Flow<Int>.merge(other: Flow<Int>): Flow<Int> = callbackFlow {
+ launch {
+ collect { send(it) }
+ }
+ other.collect { send(it) }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/flow/CombineStressTest.kt b/kotlinx-coroutines-core/jvm/test/flow/CombineStressTest.kt
new file mode 100644
index 00000000..3b5c36f9
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/flow/CombineStressTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2019 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.*
+
+class CombineStressTest : TestBase() {
+
+ @Test
+ public fun testCancellation() = runTest {
+ withContext(Dispatchers.Default + CoroutineExceptionHandler { _, _ -> expectUnreached() }) {
+ flow {
+ expect(1)
+ repeat(1_000 * stressTestMultiplier) {
+ emit(it)
+ }
+ }.flatMapLatest {
+ combine(flowOf(it), flowOf(it)) { arr -> arr[0] }
+ }.collect()
+ finish(2)
+ reset()
+ }
+ }
+
+ @Test
+ public fun testFailure() = runTest {
+ val innerIterations = 100 * stressTestMultiplierSqrt
+ val outerIterations = 10 * stressTestMultiplierSqrt
+ withContext(Dispatchers.Default + CoroutineExceptionHandler { _, _ -> expectUnreached() }) {
+ repeat(outerIterations) {
+ try {
+ flow {
+ expect(1)
+ repeat(innerIterations) {
+ emit(it)
+ }
+ }.flatMapLatest {
+ combine(flowOf(it), flowOf(it)) { arr -> arr[0] }
+ }.onEach {
+ if (it >= innerIterations / 2) throw TestException()
+ }.collect()
+ } catch (e: TestException) {
+ expect(2)
+ }
+ finish(3)
+ reset()
+ }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/flow/FlatMapStressTest.kt b/kotlinx-coroutines-core/jvm/test/flow/FlatMapStressTest.kt
new file mode 100644
index 00000000..699d9c64
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/flow/FlatMapStressTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016-2019 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.flow.internal.*
+import kotlinx.coroutines.scheduling.*
+import org.junit.Assume.*
+import org.junit.Test
+import java.util.concurrent.atomic.*
+import kotlin.test.*
+
+class FlatMapStressTest : TestBase() {
+
+ private val iterations = 2000 * stressTestMultiplier
+ private val expectedSum = iterations.toLong() * (iterations + 1) / 2
+
+ @Test
+ fun testConcurrencyLevel() = runTest {
+ withContext(Dispatchers.Default) {
+ testConcurrencyLevel(2)
+ }
+ }
+
+ @Test
+ fun testConcurrencyLevel2() = runTest {
+ withContext(Dispatchers.Default) {
+ testConcurrencyLevel(3)
+ }
+ }
+
+ @Test
+ fun testBufferSize() = runTest {
+ val bufferSize = 5
+ withContext(Dispatchers.Default) {
+ val inFlightElements = AtomicLong(0L)
+ var result = 0L
+ (1..iterations step 4).asFlow().flatMapMerge { value ->
+ unsafeFlow {
+ repeat(4) {
+ emit(value + it)
+ inFlightElements.incrementAndGet()
+ }
+ }
+ }.buffer(bufferSize).collect { value ->
+ val inFlight = inFlightElements.get()
+ assertTrue(inFlight <= bufferSize + 1,
+ "Expected less in flight elements than ${bufferSize + 1}, but had $inFlight")
+ inFlightElements.decrementAndGet()
+ result += value
+ }
+
+ assertEquals(0, inFlightElements.get())
+ assertEquals(expectedSum, result)
+ }
+ }
+
+ @Test
+ fun testDelivery() = runTest {
+ withContext(Dispatchers.Default) {
+ val result = (1L..iterations step 4).asFlow().flatMapMerge { value ->
+ unsafeFlow {
+ repeat(4) { emit(value + it) }
+ }
+ }.longSum()
+ assertEquals(expectedSum, result)
+ }
+ }
+
+ @Test
+ fun testIndependentShortBursts() = runTest {
+ withContext(Dispatchers.Default) {
+ repeat(iterations) {
+ val result = (1L..4L).asFlow().flatMapMerge { value ->
+ unsafeFlow {
+ emit(value)
+ emit(value)
+ }
+ }.longSum()
+ assertEquals(20, result)
+ }
+ }
+ }
+
+ private suspend fun testConcurrencyLevel(maxConcurrency: Int) {
+ assumeTrue(maxConcurrency <= CORE_POOL_SIZE)
+ val concurrency = AtomicLong()
+ val result = (1L..iterations).asFlow().flatMapMerge(concurrency = maxConcurrency) { value ->
+ unsafeFlow {
+ val current = concurrency.incrementAndGet()
+ assertTrue(current in 1..maxConcurrency)
+ emit(value)
+ concurrency.decrementAndGet()
+ }
+ }.longSum()
+
+ assertEquals(0, concurrency.get())
+ assertEquals(expectedSum, result)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt
new file mode 100644
index 00000000..dcb36af5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic01
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt
new file mode 100644
index 00000000..4f1277d4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic02
+
+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
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt
new file mode 100644
index 00000000..a78840d5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic02b
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt
new file mode 100644
index 00000000..a35e8481
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic03
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ 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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt
new file mode 100644
index 00000000..13cf679a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic03s
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking { // this: CoroutineScope
+ launch { // launch a new coroutine in the scope of runBlocking
+ delay(1000L)
+ println("World!")
+ }
+ 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
new file mode 100644
index 00000000..3afa0fe5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic04
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt
new file mode 100644
index 00000000..e6a91129
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic05
+
+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-05s.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05s.kt
new file mode 100644
index 00000000..876f9a1a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05s.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic05s
+
+import kotlinx.coroutines.*
+
+fun main(args: Array<String>) = runBlocking {
+ launchDoWorld()
+ println("Hello,")
+}
+
+// this is your first suspending function
+suspend fun launchDoWorld() = coroutineScope {
+ launch {
+ println("World!")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt
new file mode 100644
index 00000000..60de941d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic06
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ repeat(100_000) { // launch a lot of coroutines
+ launch {
+ delay(1000L)
+ print(".")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt
new file mode 100644
index 00000000..56e785fb
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.basic07
+
+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-cancel-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt
new file mode 100644
index 00000000..ebf5171e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.cancel01
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ 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.")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt
new file mode 100644
index 00000000..e3127b41
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.cancel02
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ val startTime = 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 (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.")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
new file mode 100644
index 00000000..d47ecd9d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.cancel03
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ val startTime = currentTimeMillis()
+ val job = launch(Dispatchers.Default) {
+ var nextPrintTime = startTime
+ var i = 0
+ while (isActive) { // cancellable computation loop
+ // print a message twice a second
+ if (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.")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
new file mode 100644
index 00000000..45c97851
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.cancel04
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ 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.")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
new file mode 100644
index 00000000..9f2cac1c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.cancel05
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ 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.")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
new file mode 100644
index 00000000..f06d1187
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.cancel06
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ withTimeout(1300L) {
+ repeat(1000) { i ->
+ println("I'm sleeping $i ...")
+ delay(500L)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
new file mode 100644
index 00000000..e2880c91
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.cancel07
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ 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")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt
new file mode 100644
index 00000000..36c6db31
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel01
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+ 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!")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt
new file mode 100644
index 00000000..59f5a768
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel02
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+ 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!")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt
new file mode 100644
index 00000000..5c9cfb18
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel03
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
+ for (x in 1..5) send(x * x)
+}
+
+fun main() = runBlocking {
+ val squares = produceSquares()
+ squares.consumeEach { println(it) }
+ println("Done!")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt
new file mode 100644
index 00000000..4eb6c37d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel04
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+ val numbers = produceNumbers() // produces integers from 1 and on
+ val squares = square(numbers) // squares integers
+ for (i in 1..5) println(squares.receive()) // print first five
+ println("Done!") // we are done
+ coroutineContext.cancelChildren() // cancel children coroutines
+}
+
+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)
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt
new file mode 100644
index 00000000..8b80764a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel05
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+ var cur = numbersFrom(2)
+ for (i in 1..10) {
+ val prime = cur.receive()
+ println(prime)
+ cur = filter(cur, prime)
+ }
+ coroutineContext.cancelChildren() // cancel all children to let main finish
+}
+
+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)
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt
new file mode 100644
index 00000000..452e056d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel06
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking<Unit> {
+ val producer = produceNumbers()
+ repeat(5) { launchProcessor(it, producer) }
+ delay(950)
+ producer.cancel() // cancel producer coroutine and thus kill them all
+}
+
+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")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt
new file mode 100644
index 00000000..9fc852e5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel07
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+ 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
+}
+
+suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
+ while (true) {
+ delay(time)
+ channel.send(s)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt
new file mode 100644
index 00000000..c9916d41
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel08
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking<Unit> {
+ 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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt
new file mode 100644
index 00000000..fb293257
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel09
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+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
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt
new file mode 100644
index 00000000..43ceea50
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.channel10
+
+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") // initial delay hasn't passed yet
+
+ nextElement = withTimeoutOrNull(50) { tickerChannel.receive() } // all subsequent elements has 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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt
new file mode 100644
index 00000000..ab9ef608
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.compose01
+
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+fun main() = runBlocking<Unit> {
+ val time = measureTimeMillis {
+ val one = doSomethingUsefulOne()
+ val two = doSomethingUsefulTwo()
+ println("The answer is ${one + two}")
+ }
+ println("Completed in $time ms")
+}
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt
new file mode 100644
index 00000000..9e46c6c4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.compose02
+
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+fun main() = runBlocking<Unit> {
+ val time = measureTimeMillis {
+ val one = async { doSomethingUsefulOne() }
+ val two = async { doSomethingUsefulTwo() }
+ println("The answer is ${one.await() + two.await()}")
+ }
+ println("Completed in $time ms")
+}
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt
new file mode 100644
index 00000000..1dc2fd9b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.compose03
+
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+fun main() = runBlocking<Unit> {
+ 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")
+}
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt
new file mode 100644
index 00000000..ad0b0214
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.compose04
+
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+// 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")
+}
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt
new file mode 100644
index 00000000..e02f33e0
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.compose05
+
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+fun main() = runBlocking<Unit> {
+ val time = measureTimeMillis {
+ println("The answer is ${concurrentSum()}")
+ }
+ println("Completed in $time ms")
+}
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt
new file mode 100644
index 00000000..1df506e4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.compose06
+
+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()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt
new file mode 100644
index 00000000..c3a9f5af
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context01
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ 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}")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt
new file mode 100644
index 00000000..d1ec85fa
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context02
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ 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}")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt
new file mode 100644
index 00000000..e52976d0
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context03
+
+import kotlinx.coroutines.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+fun main() = runBlocking<Unit> {
+ 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()}")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt
new file mode 100644
index 00000000..b4a8a3f8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context04
+
+import kotlinx.coroutines.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+fun main() {
+ newSingleThreadContext("Ctx1").use { ctx1 ->
+ newSingleThreadContext("Ctx2").use { ctx2 ->
+ runBlocking(ctx1) {
+ log("Started in ctx1")
+ withContext(ctx2) {
+ log("Working in ctx2")
+ }
+ log("Back to ctx1")
+ }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt
new file mode 100644
index 00000000..338e3c9d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context05
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ println("My job is ${coroutineContext[Job]}")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
new file mode 100644
index 00000000..b37b06b8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context06
+
+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!")
+ 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?")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt
new file mode 100644
index 00000000..825f572a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context07
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ // 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")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt
new file mode 100644
index 00000000..1083d77d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context08
+
+import kotlinx.coroutines.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+fun main() = runBlocking(CoroutineName("main")) {
+ 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()}")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt
new file mode 100644
index 00000000..386e5254
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context09
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ launch(Dispatchers.Default + CoroutineName("test")) {
+ println("I'm working in thread ${Thread.currentThread().name}")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt
new file mode 100644
index 00000000..3dde4467
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context10
+
+import kotlin.coroutines.*
+import kotlinx.coroutines.*
+
+class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
+
+ fun destroy() {
+ cancel() // Extension on CoroutineScope
+ }
+ // to be continued ...
+
+ // class Activity continues
+ fun doSomething() {
+ // launch ten coroutines for a demo, each working for a different time
+ repeat(10) { i ->
+ launch {
+ delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
+ println("Coroutine $i is done")
+ }
+ }
+ }
+} // class Activity ends
+
+fun main() = runBlocking<Unit> {
+ 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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt
new file mode 100644
index 00000000..4a50d86c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.context11
+
+import kotlinx.coroutines.*
+
+val threadLocal = ThreadLocal<String?>() // declare thread-local variable
+
+fun main() = runBlocking<Unit> {
+ 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()}'")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt
new file mode 100644
index 00000000..4bec14fc
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.exceptions01
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ val job = GlobalScope.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 {
+ 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")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt
new file mode 100644
index 00000000..818ab285
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.exceptions02
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("Caught $exception")
+ }
+ val job = GlobalScope.launch(handler) {
+ throw AssertionError()
+ }
+ val deferred = GlobalScope.async(handler) {
+ throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
+ }
+ joinAll(job, deferred)
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt
new file mode 100644
index 00000000..2b1e8e62
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.exceptions03
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ 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()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt
new file mode 100644
index 00000000..02024ce2
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.exceptions04
+
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("Caught $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()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt
new file mode 100644
index 00000000..e90606ff
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.exceptions05
+
+import kotlinx.coroutines.exceptions.*
+
+import kotlinx.coroutines.*
+import java.io.*
+
+fun main() = runBlocking {
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("Caught $exception with suppressed ${exception.suppressed.contentToString()}")
+ }
+ val job = GlobalScope.launch(handler) {
+ launch {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw ArithmeticException()
+ }
+ }
+ launch {
+ delay(100)
+ throw IOException()
+ }
+ delay(Long.MAX_VALUE)
+ }
+ job.join()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt
new file mode 100644
index 00000000..636c4a1f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.exceptions06
+
+import kotlinx.coroutines.*
+import java.io.*
+
+fun main() = runBlocking {
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("Caught original $exception")
+ }
+ val job = GlobalScope.launch(handler) {
+ val inner = launch {
+ launch {
+ launch {
+ throw IOException()
+ }
+ }
+ }
+ try {
+ inner.join()
+ } catch (e: CancellationException) {
+ println("Rethrowing CancellationException with original cause")
+ throw e
+ }
+ }
+ job.join()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt
new file mode 100644
index 00000000..020f458b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow01
+
+fun foo(): List<Int> = listOf(1, 2, 3)
+
+fun main() {
+ foo().forEach { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-02.kt
new file mode 100644
index 00000000..66fc1639
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-02.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow02
+
+fun foo(): 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() {
+ foo().forEach { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-03.kt
new file mode 100644
index 00000000..393a0fa3
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-03.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow03
+
+import kotlinx.coroutines.*
+
+suspend fun foo(): List<Int> {
+ delay(1000) // pretend we are doing something asynchronous here
+ return listOf(1, 2, 3)
+}
+
+fun main() = runBlocking<Unit> {
+ foo().forEach { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-04.kt
new file mode 100644
index 00000000..9a3c05cd
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-04.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow04
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): 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 see that the main thread is not blocked
+ launch {
+ for (k in 1..3) {
+ println("I'm not blocked $k")
+ delay(100)
+ }
+ }
+ // Collect the flow
+ foo().collect { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt
new file mode 100644
index 00000000..c1e05e2e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow05
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = flow {
+ println("Flow started")
+ for (i in 1..3) {
+ delay(100)
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ println("Calling foo...")
+ val flow = foo()
+ println("Calling collect...")
+ flow.collect { value -> println(value) }
+ println("Calling collect again...")
+ flow.collect { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt
new file mode 100644
index 00000000..1926983d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow06
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = flow {
+ for (i in 1..3) {
+ delay(100)
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ withTimeoutOrNull(250) { // Timeout after 250ms
+ foo().collect { value -> println(value) }
+ }
+ println("Done")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt
new file mode 100644
index 00000000..47ecf20b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow07
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+ // Convert an integer range to a flow
+ (1..3).asFlow().collect { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-08.kt
new file mode 100644
index 00000000..96aa19c1
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-08.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow08
+
+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> {
+ (1..3).asFlow() // a flow of requests
+ .map { request -> performRequest(request) }
+ .collect { response -> println(response) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-09.kt
new file mode 100644
index 00000000..4af29d93
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-09.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow09
+
+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> {
+ (1..3).asFlow() // a flow of requests
+ .transform { request ->
+ emit("Making request $request")
+ emit(performRequest(request))
+ }
+ .collect { response -> println(response) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt
new file mode 100644
index 00000000..47602dab
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow10
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+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) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-11.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-11.kt
new file mode 100644
index 00000000..a9740062
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-11.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow11
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+ 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)
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-12.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-12.kt
new file mode 100644
index 00000000..24dc4267
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-12.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow12
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+ (1..5).asFlow()
+ .filter {
+ println("Filter $it")
+ it % 2 == 0
+ }
+ .map {
+ println("Map $it")
+ "string $it"
+ }.collect {
+ println("Collect $it")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt
new file mode 100644
index 00000000..5d45946c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow13
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+fun foo(): Flow<Int> = flow {
+ log("Started foo flow")
+ for (i in 1..3) {
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ foo().collect { value -> log("Collected $value") }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt
new file mode 100644
index 00000000..6996abcd
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow14
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = flow {
+ // 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> {
+ foo().collect { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-15.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-15.kt
new file mode 100644
index 00000000..3c1b10a9
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-15.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow15
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+fun foo(): 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> {
+ foo().collect { value ->
+ log("Collected $value")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt
new file mode 100644
index 00000000..0698e1bf
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow16
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.system.*
+
+fun foo(): 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 {
+ foo().collect { value ->
+ delay(300) // pretend we are processing it for 300 ms
+ println(value)
+ }
+ }
+ println("Collected in $time ms")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt
new file mode 100644
index 00000000..86de59a2
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow17
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.system.*
+
+fun foo(): 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 {
+ foo()
+ .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")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt
new file mode 100644
index 00000000..597ff783
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow18
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.system.*
+
+fun foo(): 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 {
+ foo()
+ .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")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-19.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-19.kt
new file mode 100644
index 00000000..eff3d8c0
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-19.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow19
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.system.*
+
+fun foo(): 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 {
+ foo()
+ .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")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-20.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-20.kt
new file mode 100644
index 00000000..0cc3df45
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-20.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow20
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+ 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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-21.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-21.kt
new file mode 100644
index 00000000..5bf0e870
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-21.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow21
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+ 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 = 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 ${currentTimeMillis() - startTime} ms from start")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-22.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-22.kt
new file mode 100644
index 00000000..cd8f8b0c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-22.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow22
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+ 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 = 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 ${currentTimeMillis() - startTime} ms from start")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-23.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-23.kt
new file mode 100644
index 00000000..742452e4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-23.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow23
+
+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> {
+ val startTime = 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 ${currentTimeMillis() - startTime} ms from start")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-24.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-24.kt
new file mode 100644
index 00000000..32047a93
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-24.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow24
+
+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> {
+ val startTime = 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 ${currentTimeMillis() - startTime} ms from start")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt
new file mode 100644
index 00000000..09455309
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow25
+
+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> {
+ val startTime = 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 ${currentTimeMillis() - startTime} ms from start")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt
new file mode 100644
index 00000000..037e2530
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow26
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i) // emit next value
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ try {
+ foo().collect { value ->
+ println(value)
+ check(value <= 1) { "Collected $value" }
+ }
+ } catch (e: Throwable) {
+ println("Caught $e")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-27.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-27.kt
new file mode 100644
index 00000000..6117cf5c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-27.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow27
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): 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 {
+ foo().collect { value -> println(value) }
+ } catch (e: Throwable) {
+ println("Caught $e")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-28.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-28.kt
new file mode 100644
index 00000000..15acb796
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-28.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow28
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): 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> {
+ foo()
+ .catch { e -> emit("Caught $e") } // emit on exception
+ .collect { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt
new file mode 100644
index 00000000..c0497df7
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow29
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ foo()
+ .catch { e -> println("Caught $e") } // does not catch downstream exceptions
+ .collect { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt
new file mode 100644
index 00000000..5035efe2
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow30
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ foo()
+ .onEach { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+ .catch { e -> println("Caught $e") }
+ .collect()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt
new file mode 100644
index 00000000..dfa43db5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow31
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+ try {
+ foo().collect { value -> println(value) }
+ } finally {
+ println("Done")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-32.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-32.kt
new file mode 100644
index 00000000..f541ab57
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-32.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow32
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+ foo()
+ .onCompletion { println("Done") }
+ .collect { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-33.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-33.kt
new file mode 100644
index 00000000..1e291412
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-33.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow33
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = flow {
+ emit(1)
+ throw RuntimeException()
+}
+
+fun main() = runBlocking<Unit> {
+ foo()
+ .onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
+ .catch { cause -> println("Caught exception") }
+ .collect { value -> println(value) }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt
new file mode 100644
index 00000000..df2cad20
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow34
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun foo(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+ foo()
+ .onCompletion { cause -> println("Flow completed with $cause") }
+ .collect { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-35.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-35.kt
new file mode 100644
index 00000000..a7c6bd21
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-35.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow35
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+// 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")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-flow-36.kt b/kotlinx-coroutines-core/jvm/test/guide/example-flow-36.kt
new file mode 100644
index 00000000..9c5a57bf
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-flow-36.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.flow36
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+// 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") }
+ .launchIn(this) // <--- Launching the flow in a separate coroutine
+ println("Done")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt
new file mode 100644
index 00000000..1939e720
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.select01
+
+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> {
+ val fizz = fizz()
+ val buzz = buzz()
+ repeat(7) {
+ selectFizzBuzz(fizz, buzz)
+ }
+ coroutineContext.cancelChildren() // cancel fizz & buzz coroutines
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt
new file mode 100644
index 00000000..0e510151
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.select02
+
+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> {
+ 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()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt
new file mode 100644
index 00000000..42422378
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.select03
+
+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> {
+ 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()
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt
new file mode 100644
index 00000000..2db51702
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.select04
+
+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> {
+ 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")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt
new file mode 100644
index 00000000..e03be9da
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.select05
+
+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> {
+ 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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt
new file mode 100644
index 00000000..d70b6c9f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.supervision01
+
+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("First child is failing")
+ throw AssertionError("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("First child is cancelled: ${firstChild.isCancelled}, but second one is still active")
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ // But cancellation of the supervisor is propagated
+ println("Second child is cancelled because supervisor is cancelled")
+ }
+ }
+ // wait until the first child fails & completes
+ firstChild.join()
+ println("Cancelling supervisor")
+ supervisor.cancel()
+ secondChild.join()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt
new file mode 100644
index 00000000..facc2e08
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.supervision02
+
+import kotlin.coroutines.*
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ try {
+ supervisorScope {
+ val child = launch {
+ try {
+ println("Child is sleeping")
+ delay(Long.MAX_VALUE)
+ } finally {
+ println("Child is cancelled")
+ }
+ }
+ // Give our child a chance to execute and print using yield
+ yield()
+ println("Throwing exception from scope")
+ throw AssertionError()
+ }
+ } catch(e: AssertionError) {
+ println("Caught assertion error")
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt
new file mode 100644
index 00000000..47c31b9e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.supervision03
+
+import kotlin.coroutines.*
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("Caught $exception")
+ }
+ supervisorScope {
+ val child = launch(handler) {
+ println("Child throws an exception")
+ throw AssertionError()
+ }
+ println("Scope is completing")
+ }
+ println("Scope is completed")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt
new file mode 100644
index 00000000..bd710a49
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.sync01
+
+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")
+}
+
+var counter = 0
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ counter++
+ }
+ }
+ println("Counter = $counter")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt
new file mode 100644
index 00000000..81312372
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.sync02
+
+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")
+}
+
+@Volatile // in Kotlin `volatile` is an annotation
+var counter = 0
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ counter++
+ }
+ }
+ println("Counter = $counter")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt
new file mode 100644
index 00000000..1baf849a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.sync03
+
+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")
+}
+
+var counter = AtomicInteger()
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ counter.incrementAndGet()
+ }
+ }
+ println("Counter = $counter")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt
new file mode 100644
index 00000000..e16a8113
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.sync04
+
+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")
+}
+
+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")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt
new file mode 100644
index 00000000..d022961b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.sync05
+
+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")
+}
+
+val counterContext = newSingleThreadContext("CounterContext")
+var counter = 0
+
+fun main() = runBlocking {
+ // confine everything to a single-threaded context
+ withContext(counterContext) {
+ massiveRun {
+ counter++
+ }
+ }
+ println("Counter = $counter")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt
new file mode 100644
index 00000000..fe08f049
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.sync06
+
+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")
+}
+
+val mutex = Mutex()
+var counter = 0
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ // protect each increment with lock
+ mutex.withLock {
+ counter++
+ }
+ }
+ }
+ println("Counter = $counter")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt
new file mode 100644
index 00000000..65e20500
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.sync07
+
+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)
+ }
+ }
+}
+
+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
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt
new file mode 100644
index 00000000..93b49a60
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt
@@ -0,0 +1,81 @@
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class BasicsGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic01() {
+ test("KotlinxCoroutinesGuideBasic01") { kotlinx.coroutines.guide.basic01.main() }.verifyLines(
+ "Hello,",
+ "World!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic02() {
+ test("KotlinxCoroutinesGuideBasic02") { kotlinx.coroutines.guide.basic02.main() }.verifyLines(
+ "Hello,",
+ "World!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic02b() {
+ test("KotlinxCoroutinesGuideBasic02b") { kotlinx.coroutines.guide.basic02b.main() }.verifyLines(
+ "Hello,",
+ "World!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic03() {
+ test("KotlinxCoroutinesGuideBasic03") { kotlinx.coroutines.guide.basic03.main() }.verifyLines(
+ "Hello,",
+ "World!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic03s() {
+ test("KotlinxCoroutinesGuideBasic03s") { kotlinx.coroutines.guide.basic03s.main() }.verifyLines(
+ "Hello,",
+ "World!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic04() {
+ test("KotlinxCoroutinesGuideBasic04") { kotlinx.coroutines.guide.basic04.main() }.verifyLines(
+ "Task from coroutine scope",
+ "Task from runBlocking",
+ "Task from nested launch",
+ "Coroutine scope is over"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic05() {
+ test("KotlinxCoroutinesGuideBasic05") { kotlinx.coroutines.guide.basic05.main() }.verifyLines(
+ "Hello,",
+ "World!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic06() {
+ test("KotlinxCoroutinesGuideBasic06") { kotlinx.coroutines.guide.basic06.main() }.also { lines ->
+ check(lines.size == 1 && lines[0] == ".".repeat(100_000))
+ }
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideBasic07() {
+ test("KotlinxCoroutinesGuideBasic07") { kotlinx.coroutines.guide.basic07.main() }.verifyLines(
+ "I'm sleeping 0 ...",
+ "I'm sleeping 1 ...",
+ "I'm sleeping 2 ..."
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/CancellationTimeOutsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/CancellationTimeOutsGuideTest.kt
new file mode 100644
index 00000000..83bca486
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/CancellationTimeOutsGuideTest.kt
@@ -0,0 +1,87 @@
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class CancellationTimeOutsGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideCancel01() {
+ test("KotlinxCoroutinesGuideCancel01") { kotlinx.coroutines.guide.cancel01.main() }.verifyLines(
+ "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
+ fun testKotlinxCoroutinesGuideCancel02() {
+ test("KotlinxCoroutinesGuideCancel02") { kotlinx.coroutines.guide.cancel02.main() }.verifyLines(
+ "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."
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideCancel03() {
+ test("KotlinxCoroutinesGuideCancel03") { kotlinx.coroutines.guide.cancel03.main() }.verifyLines(
+ "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
+ fun testKotlinxCoroutinesGuideCancel04() {
+ test("KotlinxCoroutinesGuideCancel04") { kotlinx.coroutines.guide.cancel04.main() }.verifyLines(
+ "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
+ fun testKotlinxCoroutinesGuideCancel05() {
+ test("KotlinxCoroutinesGuideCancel05") { kotlinx.coroutines.guide.cancel05.main() }.verifyLines(
+ "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."
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideCancel06() {
+ test("KotlinxCoroutinesGuideCancel06") { kotlinx.coroutines.guide.cancel06.main() }.verifyLinesStartWith(
+ "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
+ fun testKotlinxCoroutinesGuideCancel07() {
+ test("KotlinxCoroutinesGuideCancel07") { kotlinx.coroutines.guide.cancel07.main() }.verifyLines(
+ "I'm sleeping 0 ...",
+ "I'm sleeping 1 ...",
+ "I'm sleeping 2 ...",
+ "Result is null"
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/ChannelsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/ChannelsGuideTest.kt
new file mode 100644
index 00000000..a747c984
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/ChannelsGuideTest.kt
@@ -0,0 +1,123 @@
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class ChannelsGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel01() {
+ test("KotlinxCoroutinesGuideChannel01") { kotlinx.coroutines.guide.channel01.main() }.verifyLines(
+ "1",
+ "4",
+ "9",
+ "16",
+ "25",
+ "Done!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel02() {
+ test("KotlinxCoroutinesGuideChannel02") { kotlinx.coroutines.guide.channel02.main() }.verifyLines(
+ "1",
+ "4",
+ "9",
+ "16",
+ "25",
+ "Done!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel03() {
+ test("KotlinxCoroutinesGuideChannel03") { kotlinx.coroutines.guide.channel03.main() }.verifyLines(
+ "1",
+ "4",
+ "9",
+ "16",
+ "25",
+ "Done!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel04() {
+ test("KotlinxCoroutinesGuideChannel04") { kotlinx.coroutines.guide.channel04.main() }.verifyLines(
+ "1",
+ "4",
+ "9",
+ "16",
+ "25",
+ "Done!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel05() {
+ test("KotlinxCoroutinesGuideChannel05") { kotlinx.coroutines.guide.channel05.main() }.verifyLines(
+ "2",
+ "3",
+ "5",
+ "7",
+ "11",
+ "13",
+ "17",
+ "19",
+ "23",
+ "29"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel06() {
+ test("KotlinxCoroutinesGuideChannel06") { kotlinx.coroutines.guide.channel06.main() }.also { lines ->
+ check(lines.size == 10 && lines.withIndex().all { (i, line) -> line.startsWith("Processor #") && line.endsWith(" received ${i + 1}") })
+ }
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel07() {
+ test("KotlinxCoroutinesGuideChannel07") { kotlinx.coroutines.guide.channel07.main() }.verifyLines(
+ "foo",
+ "foo",
+ "BAR!",
+ "foo",
+ "foo",
+ "BAR!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel08() {
+ test("KotlinxCoroutinesGuideChannel08") { kotlinx.coroutines.guide.channel08.main() }.verifyLines(
+ "Sending 0",
+ "Sending 1",
+ "Sending 2",
+ "Sending 3",
+ "Sending 4"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel09() {
+ test("KotlinxCoroutinesGuideChannel09") { kotlinx.coroutines.guide.channel09.main() }.verifyLines(
+ "ping Ball(hits=1)",
+ "pong Ball(hits=2)",
+ "ping Ball(hits=3)",
+ "pong Ball(hits=4)"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideChannel10() {
+ test("KotlinxCoroutinesGuideChannel10") { kotlinx.coroutines.guide.channel10.main() }.verifyLines(
+ "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"
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/ComposingGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/ComposingGuideTest.kt
new file mode 100644
index 00000000..de4cba44
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/ComposingGuideTest.kt
@@ -0,0 +1,56 @@
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class ComposingGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideCompose01() {
+ test("KotlinxCoroutinesGuideCompose01") { kotlinx.coroutines.guide.compose01.main() }.verifyLinesArbitraryTime(
+ "The answer is 42",
+ "Completed in 2017 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideCompose02() {
+ test("KotlinxCoroutinesGuideCompose02") { kotlinx.coroutines.guide.compose02.main() }.verifyLinesArbitraryTime(
+ "The answer is 42",
+ "Completed in 1017 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideCompose03() {
+ test("KotlinxCoroutinesGuideCompose03") { kotlinx.coroutines.guide.compose03.main() }.verifyLinesArbitraryTime(
+ "The answer is 42",
+ "Completed in 1017 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideCompose04() {
+ test("KotlinxCoroutinesGuideCompose04") { kotlinx.coroutines.guide.compose04.main() }.verifyLinesArbitraryTime(
+ "The answer is 42",
+ "Completed in 1085 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideCompose05() {
+ test("KotlinxCoroutinesGuideCompose05") { kotlinx.coroutines.guide.compose05.main() }.verifyLinesArbitraryTime(
+ "The answer is 42",
+ "Completed in 1017 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideCompose06() {
+ test("KotlinxCoroutinesGuideCompose06") { kotlinx.coroutines.guide.compose06.main() }.verifyLines(
+ "Second child throws an exception",
+ "First child was cancelled",
+ "Computation failed with ArithmeticException"
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
new file mode 100644
index 00000000..180738f8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
@@ -0,0 +1,110 @@
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class DispatchersGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideContext01() {
+ test("KotlinxCoroutinesGuideContext01") { kotlinx.coroutines.guide.context01.main() }.verifyLinesStartUnordered(
+ "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
+ fun testKotlinxCoroutinesGuideContext02() {
+ test("KotlinxCoroutinesGuideContext02") { kotlinx.coroutines.guide.context02.main() }.verifyLinesStart(
+ "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
+ fun testKotlinxCoroutinesGuideContext03() {
+ test("KotlinxCoroutinesGuideContext03") { kotlinx.coroutines.guide.context03.main() }.verifyLinesFlexibleThread(
+ "[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
+ fun testKotlinxCoroutinesGuideContext04() {
+ test("KotlinxCoroutinesGuideContext04") { kotlinx.coroutines.guide.context04.main() }.verifyLines(
+ "[Ctx1 @coroutine#1] Started in ctx1",
+ "[Ctx2 @coroutine#1] Working in ctx2",
+ "[Ctx1 @coroutine#1] Back to ctx1"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideContext05() {
+ test("KotlinxCoroutinesGuideContext05") { kotlinx.coroutines.guide.context05.main() }.also { lines ->
+ check(lines.size == 1 && lines[0].startsWith("My job is \"coroutine#1\":BlockingCoroutine{Active}@"))
+ }
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideContext06() {
+ test("KotlinxCoroutinesGuideContext06") { kotlinx.coroutines.guide.context06.main() }.verifyLines(
+ "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
+ fun testKotlinxCoroutinesGuideContext07() {
+ test("KotlinxCoroutinesGuideContext07") { kotlinx.coroutines.guide.context07.main() }.verifyLines(
+ "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
+ fun testKotlinxCoroutinesGuideContext08() {
+ test("KotlinxCoroutinesGuideContext08") { kotlinx.coroutines.guide.context08.main() }.verifyLinesFlexibleThread(
+ "[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
+ fun testKotlinxCoroutinesGuideContext09() {
+ test("KotlinxCoroutinesGuideContext09") { kotlinx.coroutines.guide.context09.main() }.verifyLinesFlexibleThread(
+ "I'm working in thread DefaultDispatcher-worker-1 @test#2"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideContext10() {
+ test("KotlinxCoroutinesGuideContext10") { kotlinx.coroutines.guide.context10.main() }.verifyLines(
+ "Launched coroutines",
+ "Coroutine 0 is done",
+ "Coroutine 1 is done",
+ "Destroying activity!"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideContext11() {
+ test("KotlinxCoroutinesGuideContext11") { kotlinx.coroutines.guide.context11.main() }.verifyLinesFlexibleThread(
+ "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'"
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/ExceptionsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/ExceptionsGuideTest.kt
new file mode 100644
index 00000000..7fa692b2
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/ExceptionsGuideTest.kt
@@ -0,0 +1,89 @@
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class ExceptionsGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideExceptions01() {
+ test("KotlinxCoroutinesGuideExceptions01") { kotlinx.coroutines.guide.exceptions01.main() }.verifyExceptions(
+ "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
+ fun testKotlinxCoroutinesGuideExceptions02() {
+ test("KotlinxCoroutinesGuideExceptions02") { kotlinx.coroutines.guide.exceptions02.main() }.verifyLines(
+ "Caught java.lang.AssertionError"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideExceptions03() {
+ test("KotlinxCoroutinesGuideExceptions03") { kotlinx.coroutines.guide.exceptions03.main() }.verifyLines(
+ "Cancelling child",
+ "Child is cancelled",
+ "Parent is not cancelled"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideExceptions04() {
+ test("KotlinxCoroutinesGuideExceptions04") { kotlinx.coroutines.guide.exceptions04.main() }.verifyLines(
+ "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",
+ "Caught java.lang.ArithmeticException"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideExceptions05() {
+ test("KotlinxCoroutinesGuideExceptions05") { kotlinx.coroutines.guide.exceptions05.main() }.verifyLines(
+ "Caught java.io.IOException with suppressed [java.lang.ArithmeticException]"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideExceptions06() {
+ test("KotlinxCoroutinesGuideExceptions06") { kotlinx.coroutines.guide.exceptions06.main() }.verifyLines(
+ "Rethrowing CancellationException with original cause",
+ "Caught original java.io.IOException"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSupervision01() {
+ test("KotlinxCoroutinesGuideSupervision01") { kotlinx.coroutines.guide.supervision01.main() }.verifyLines(
+ "First child is failing",
+ "First child is cancelled: true, but second one is still active",
+ "Cancelling supervisor",
+ "Second child is cancelled because supervisor is cancelled"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSupervision02() {
+ test("KotlinxCoroutinesGuideSupervision02") { kotlinx.coroutines.guide.supervision02.main() }.verifyLines(
+ "Child is sleeping",
+ "Throwing exception from scope",
+ "Child is cancelled",
+ "Caught assertion error"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSupervision03() {
+ test("KotlinxCoroutinesGuideSupervision03") { kotlinx.coroutines.guide.supervision03.main() }.verifyLines(
+ "Scope is completing",
+ "Child throws an exception",
+ "Caught java.lang.AssertionError",
+ "Scope is completed"
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/FlowGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/FlowGuideTest.kt
new file mode 100644
index 00000000..0353c54e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/FlowGuideTest.kt
@@ -0,0 +1,381 @@
+// This file was automatically generated from flow.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class FlowGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow01() {
+ test("KotlinxCoroutinesGuideFlow01") { kotlinx.coroutines.guide.flow01.main() }.verifyLines(
+ "1",
+ "2",
+ "3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow02() {
+ test("KotlinxCoroutinesGuideFlow02") { kotlinx.coroutines.guide.flow02.main() }.verifyLines(
+ "1",
+ "2",
+ "3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow03() {
+ test("KotlinxCoroutinesGuideFlow03") { kotlinx.coroutines.guide.flow03.main() }.verifyLines(
+ "1",
+ "2",
+ "3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow04() {
+ test("KotlinxCoroutinesGuideFlow04") { kotlinx.coroutines.guide.flow04.main() }.verifyLines(
+ "I'm not blocked 1",
+ "1",
+ "I'm not blocked 2",
+ "2",
+ "I'm not blocked 3",
+ "3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow05() {
+ test("KotlinxCoroutinesGuideFlow05") { kotlinx.coroutines.guide.flow05.main() }.verifyLines(
+ "Calling foo...",
+ "Calling collect...",
+ "Flow started",
+ "1",
+ "2",
+ "3",
+ "Calling collect again...",
+ "Flow started",
+ "1",
+ "2",
+ "3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow06() {
+ test("KotlinxCoroutinesGuideFlow06") { kotlinx.coroutines.guide.flow06.main() }.verifyLines(
+ "Emitting 1",
+ "1",
+ "Emitting 2",
+ "2",
+ "Done"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow07() {
+ test("KotlinxCoroutinesGuideFlow07") { kotlinx.coroutines.guide.flow07.main() }.verifyLines(
+ "1",
+ "2",
+ "3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow08() {
+ test("KotlinxCoroutinesGuideFlow08") { kotlinx.coroutines.guide.flow08.main() }.verifyLines(
+ "response 1",
+ "response 2",
+ "response 3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow09() {
+ test("KotlinxCoroutinesGuideFlow09") { kotlinx.coroutines.guide.flow09.main() }.verifyLines(
+ "Making request 1",
+ "response 1",
+ "Making request 2",
+ "response 2",
+ "Making request 3",
+ "response 3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow10() {
+ test("KotlinxCoroutinesGuideFlow10") { kotlinx.coroutines.guide.flow10.main() }.verifyLines(
+ "1",
+ "2",
+ "Finally in numbers"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow11() {
+ test("KotlinxCoroutinesGuideFlow11") { kotlinx.coroutines.guide.flow11.main() }.verifyLines(
+ "55"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow12() {
+ test("KotlinxCoroutinesGuideFlow12") { kotlinx.coroutines.guide.flow12.main() }.verifyLines(
+ "Filter 1",
+ "Filter 2",
+ "Map 2",
+ "Collect string 2",
+ "Filter 3",
+ "Filter 4",
+ "Map 4",
+ "Collect string 4",
+ "Filter 5"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow13() {
+ test("KotlinxCoroutinesGuideFlow13") { kotlinx.coroutines.guide.flow13.main() }.verifyLinesFlexibleThread(
+ "[main @coroutine#1] Started foo flow",
+ "[main @coroutine#1] Collected 1",
+ "[main @coroutine#1] Collected 2",
+ "[main @coroutine#1] Collected 3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow14() {
+ test("KotlinxCoroutinesGuideFlow14") { kotlinx.coroutines.guide.flow14.main() }.verifyExceptions(
+ "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, DefaultDispatcher].",
+ " Please refer to 'flow' documentation or use 'flowOn' instead",
+ " at ..."
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow15() {
+ test("KotlinxCoroutinesGuideFlow15") { kotlinx.coroutines.guide.flow15.main() }.verifyLinesFlexibleThread(
+ "[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"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow16() {
+ test("KotlinxCoroutinesGuideFlow16") { kotlinx.coroutines.guide.flow16.main() }.verifyLinesArbitraryTime(
+ "1",
+ "2",
+ "3",
+ "Collected in 1220 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow17() {
+ test("KotlinxCoroutinesGuideFlow17") { kotlinx.coroutines.guide.flow17.main() }.verifyLinesArbitraryTime(
+ "1",
+ "2",
+ "3",
+ "Collected in 1071 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow18() {
+ test("KotlinxCoroutinesGuideFlow18") { kotlinx.coroutines.guide.flow18.main() }.verifyLinesArbitraryTime(
+ "1",
+ "3",
+ "Collected in 758 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow19() {
+ test("KotlinxCoroutinesGuideFlow19") { kotlinx.coroutines.guide.flow19.main() }.verifyLinesArbitraryTime(
+ "Collecting 1",
+ "Collecting 2",
+ "Collecting 3",
+ "Done 3",
+ "Collected in 741 ms"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow20() {
+ test("KotlinxCoroutinesGuideFlow20") { kotlinx.coroutines.guide.flow20.main() }.verifyLines(
+ "1 -> one",
+ "2 -> two",
+ "3 -> three"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow21() {
+ test("KotlinxCoroutinesGuideFlow21") { kotlinx.coroutines.guide.flow21.main() }.verifyLinesArbitraryTime(
+ "1 -> one at 437 ms from start",
+ "2 -> two at 837 ms from start",
+ "3 -> three at 1243 ms from start"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow22() {
+ test("KotlinxCoroutinesGuideFlow22") { kotlinx.coroutines.guide.flow22.main() }.verifyLinesArbitraryTime(
+ "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
+ fun testKotlinxCoroutinesGuideFlow23() {
+ test("KotlinxCoroutinesGuideFlow23") { kotlinx.coroutines.guide.flow23.main() }.verifyLinesArbitraryTime(
+ "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
+ fun testKotlinxCoroutinesGuideFlow24() {
+ test("KotlinxCoroutinesGuideFlow24") { kotlinx.coroutines.guide.flow24.main() }.verifyLinesArbitraryTime(
+ "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
+ fun testKotlinxCoroutinesGuideFlow25() {
+ test("KotlinxCoroutinesGuideFlow25") { kotlinx.coroutines.guide.flow25.main() }.verifyLinesArbitraryTime(
+ "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
+ fun testKotlinxCoroutinesGuideFlow26() {
+ test("KotlinxCoroutinesGuideFlow26") { kotlinx.coroutines.guide.flow26.main() }.verifyLines(
+ "Emitting 1",
+ "1",
+ "Emitting 2",
+ "2",
+ "Caught java.lang.IllegalStateException: Collected 2"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow27() {
+ test("KotlinxCoroutinesGuideFlow27") { kotlinx.coroutines.guide.flow27.main() }.verifyLines(
+ "Emitting 1",
+ "string 1",
+ "Emitting 2",
+ "Caught java.lang.IllegalStateException: Crashed on 2"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow28() {
+ test("KotlinxCoroutinesGuideFlow28") { kotlinx.coroutines.guide.flow28.main() }.verifyLines(
+ "Emitting 1",
+ "string 1",
+ "Emitting 2",
+ "Caught java.lang.IllegalStateException: Crashed on 2"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow29() {
+ test("KotlinxCoroutinesGuideFlow29") { kotlinx.coroutines.guide.flow29.main() }.verifyExceptions(
+ "Emitting 1",
+ "1",
+ "Emitting 2",
+ "Exception in thread \"main\" java.lang.IllegalStateException: Collected 2",
+ " at ..."
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow30() {
+ test("KotlinxCoroutinesGuideFlow30") { kotlinx.coroutines.guide.flow30.main() }.verifyExceptions(
+ "Emitting 1",
+ "1",
+ "Emitting 2",
+ "Caught java.lang.IllegalStateException: Collected 2"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow31() {
+ test("KotlinxCoroutinesGuideFlow31") { kotlinx.coroutines.guide.flow31.main() }.verifyLines(
+ "1",
+ "2",
+ "3",
+ "Done"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow32() {
+ test("KotlinxCoroutinesGuideFlow32") { kotlinx.coroutines.guide.flow32.main() }.verifyLines(
+ "1",
+ "2",
+ "3",
+ "Done"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow33() {
+ test("KotlinxCoroutinesGuideFlow33") { kotlinx.coroutines.guide.flow33.main() }.verifyLines(
+ "1",
+ "Flow completed exceptionally",
+ "Caught exception"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow34() {
+ test("KotlinxCoroutinesGuideFlow34") { kotlinx.coroutines.guide.flow34.main() }.verifyExceptions(
+ "1",
+ "Flow completed with null",
+ "Exception in thread \"main\" java.lang.IllegalStateException: Collected 2"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow35() {
+ test("KotlinxCoroutinesGuideFlow35") { kotlinx.coroutines.guide.flow35.main() }.verifyLines(
+ "Event: 1",
+ "Event: 2",
+ "Event: 3",
+ "Done"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideFlow36() {
+ test("KotlinxCoroutinesGuideFlow36") { kotlinx.coroutines.guide.flow36.main() }.verifyLines(
+ "Done",
+ "Event: 1",
+ "Event: 2",
+ "Event: 3"
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt
new file mode 100644
index 00000000..b5246ff4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt
@@ -0,0 +1,69 @@
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class SelectGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideSelect01() {
+ test("KotlinxCoroutinesGuideSelect01") { kotlinx.coroutines.guide.select01.main() }.verifyLines(
+ "fizz -> 'Fizz'",
+ "buzz -> 'Buzz!'",
+ "fizz -> 'Fizz'",
+ "fizz -> 'Fizz'",
+ "buzz -> 'Buzz!'",
+ "fizz -> 'Fizz'",
+ "buzz -> 'Buzz!'"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSelect02() {
+ test("KotlinxCoroutinesGuideSelect02") { kotlinx.coroutines.guide.select02.main() }.verifyLines(
+ "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
+ fun testKotlinxCoroutinesGuideSelect03() {
+ test("KotlinxCoroutinesGuideSelect03") { kotlinx.coroutines.guide.select03.main() }.verifyLines(
+ "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
+ fun testKotlinxCoroutinesGuideSelect04() {
+ test("KotlinxCoroutinesGuideSelect04") { kotlinx.coroutines.guide.select04.main() }.verifyLines(
+ "Deferred 4 produced answer 'Waited for 128 ms'",
+ "11 coroutines are still active"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSelect05() {
+ test("KotlinxCoroutinesGuideSelect05") { kotlinx.coroutines.guide.select05.main() }.verifyLines(
+ "BEGIN",
+ "Replace",
+ "END",
+ "Channel was closed"
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt
new file mode 100644
index 00000000..45988570
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt
@@ -0,0 +1,63 @@
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.test
+
+import org.junit.Test
+
+class SharedStateGuideTest {
+
+ @Test
+ fun testKotlinxCoroutinesGuideSync01() {
+ test("KotlinxCoroutinesGuideSync01") { kotlinx.coroutines.guide.sync01.main() }.verifyLinesStart(
+ "Completed 100000 actions in",
+ "Counter ="
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSync02() {
+ test("KotlinxCoroutinesGuideSync02") { kotlinx.coroutines.guide.sync02.main() }.verifyLinesStart(
+ "Completed 100000 actions in",
+ "Counter ="
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSync03() {
+ test("KotlinxCoroutinesGuideSync03") { kotlinx.coroutines.guide.sync03.main() }.verifyLinesArbitraryTime(
+ "Completed 100000 actions in xxx ms",
+ "Counter = 100000"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSync04() {
+ test("KotlinxCoroutinesGuideSync04") { kotlinx.coroutines.guide.sync04.main() }.verifyLinesArbitraryTime(
+ "Completed 100000 actions in xxx ms",
+ "Counter = 100000"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSync05() {
+ test("KotlinxCoroutinesGuideSync05") { kotlinx.coroutines.guide.sync05.main() }.verifyLinesArbitraryTime(
+ "Completed 100000 actions in xxx ms",
+ "Counter = 100000"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSync06() {
+ test("KotlinxCoroutinesGuideSync06") { kotlinx.coroutines.guide.sync06.main() }.verifyLinesArbitraryTime(
+ "Completed 100000 actions in xxx ms",
+ "Counter = 100000"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesGuideSync07() {
+ test("KotlinxCoroutinesGuideSync07") { kotlinx.coroutines.guide.sync07.main() }.verifyLinesArbitraryTime(
+ "Completed 100000 actions in xxx ms",
+ "Counter = 100000"
+ )
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt b/kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt
new file mode 100644
index 00000000..bd7159fb
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.guide.test
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.scheduling.*
+import org.junit.Assert.*
+import java.io.*
+import java.util.concurrent.*
+
+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() }
+ catch (e: Throwable) {
+ println("--- Failed test$name")
+ e.printStackTrace(System.out)
+ throw e
+ }
+
+private const val SHUTDOWN_TIMEOUT = 5000L // 5 sec at most to wait
+private val OUT_ENABLED = systemProp("guide.tests.sout", false)
+
+@Suppress("DEPRECATION")
+fun <R> test(name: String, block: () -> R): List<String> = outputException(name) {
+ val sout = System.out
+ val oldOut = if (OUT_ENABLED) System.out else NullOut
+ val oldErr = System.err
+ val bytesOut = ByteArrayOutputStream()
+ val tee = TeeOutput(bytesOut, oldOut)
+ val ps = PrintStream(tee)
+
+ oldOut.println("--- Running test$name")
+ System.setErr(ps)
+ System.setOut(ps)
+ CommonPool.usePrivatePool()
+ DefaultScheduler.usePrivateScheduler()
+ DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT)
+ resetCoroutineId()
+ val threadsBefore = currentThreads()
+ var bytes = ByteArray(0)
+ withVirtualTimeSource(oldOut) {
+ try {
+ val result = block()
+ require(result === Unit) { "Test 'main' shall return Unit" }
+ } catch (e: Throwable) {
+ System.err.print("Exception in thread \"main\" ")
+ e.printStackTrace()
+ } finally {
+ // capture output
+ bytes = bytesOut.toByteArray()
+ oldOut.println("--- shutting down")
+ // the shutdown
+ CommonPool.shutdown(SHUTDOWN_TIMEOUT)
+ DefaultScheduler.shutdown(SHUTDOWN_TIMEOUT)
+ shutdownDispatcherPools(SHUTDOWN_TIMEOUT)
+ DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT) // the last man standing -- cleanup all pending tasks
+ if (tee.flushLine()) oldOut.println()
+ oldOut.println("--- done")
+ System.setOut(sout)
+ System.setErr(oldErr)
+ checkTestThreads(threadsBefore)
+ }
+ }
+ CommonPool.restore()
+ DefaultScheduler.restore()
+ return ByteArrayInputStream(bytes).bufferedReader().readLines()
+}
+
+private class TeeOutput(
+ private val bytesOut: OutputStream,
+ private val oldOut: PrintStream
+) : OutputStream() {
+ val limit = 200
+ var lineLength = 0
+
+ fun flushLine(): Boolean {
+ if (lineLength > limit)
+ oldOut.print(" ($lineLength chars in total)")
+ val result = lineLength > 0
+ lineLength = 0
+ return result
+ }
+
+ override fun write(b: Int) {
+ bytesOut.write(b)
+ if (b == 0x0d || b == 0x0a) { // new line
+ flushLine()
+ oldOut.write(b)
+ } else {
+ lineLength++
+ if (lineLength <= limit)
+ oldOut.write(b)
+ }
+ }
+}
+
+
+private fun shutdownDispatcherPools(timeout: Long) {
+ val threads = arrayOfNulls<Thread>(Thread.activeCount())
+ val n = Thread.enumerate(threads)
+ for (i in 0 until n) {
+ val thread = threads[i]
+ if (thread is PoolThread)
+ (thread.dispatcher.executor as ExecutorService).apply {
+ shutdown()
+ awaitTermination(timeout, TimeUnit.MILLISECONDS)
+ shutdownNow().forEach { DefaultExecutor.enqueue(it) }
+ }
+ }
+}
+
+enum class SanitizeMode {
+ NONE,
+ ARBITRARY_TIME,
+ FLEXIBLE_THREAD
+}
+
+private fun sanitize(s: String, mode: SanitizeMode): String {
+ var res = s
+ when (mode) {
+ SanitizeMode.ARBITRARY_TIME -> {
+ res = res.replace(Regex(" [0-9]+ ms"), " xxx ms")
+ }
+ SanitizeMode.FLEXIBLE_THREAD -> {
+ res = res.replace(Regex("ForkJoinPool\\.commonPool-worker-[0-9]+"), "DefaultDispatcher")
+ res = res.replace(Regex("ForkJoinPool-[0-9]+-worker-[0-9]+"), "DefaultDispatcher")
+ res = res.replace(Regex("CommonPool-worker-[0-9]+"), "DefaultDispatcher")
+ res = res.replace(Regex("DefaultDispatcher-worker-[0-9]+"), "DefaultDispatcher")
+ res = res.replace(Regex("RxComputationThreadPool-[0-9]+"), "RxComputationThreadPool")
+ res = res.replace(Regex("Test( worker)?"), "main")
+ res = res.replace(Regex("@[0-9a-f]+"), "") // drop hex address
+ }
+ SanitizeMode.NONE -> {}
+ }
+ return res
+}
+
+private fun List<String>.verifyCommonLines(expected: Array<out String>, mode: SanitizeMode = SanitizeMode.NONE) {
+ val n = minOf(size, expected.size)
+ for (i in 0 until n) {
+ val exp = sanitize(expected[i], mode)
+ val act = sanitize(get(i), mode)
+ assertEquals("Line ${i + 1}", exp, act)
+ }
+}
+
+private fun List<String>.checkEqualNumberOfLines(expected: Array<out String>) {
+ if (size > expected.size)
+ error("Expected ${expected.size} lines, but found $size. Unexpected line '${get(expected.size)}'")
+ else if (size < expected.size)
+ error("Expected ${expected.size} lines, but found $size")
+}
+
+fun List<String>.verifyLines(vararg expected: String) = verify {
+ verifyCommonLines(expected)
+ checkEqualNumberOfLines(expected)
+}
+
+fun List<String>.verifyLinesStartWith(vararg expected: String) = verify {
+ verifyCommonLines(expected)
+ assertTrue("Number of lines", expected.size <= size)
+}
+
+fun List<String>.verifyLinesArbitraryTime(vararg expected: String) = verify {
+ verifyCommonLines(expected, SanitizeMode.ARBITRARY_TIME)
+ checkEqualNumberOfLines(expected)
+}
+
+fun List<String>.verifyLinesFlexibleThread(vararg expected: String) = verify {
+ verifyCommonLines(expected, SanitizeMode.FLEXIBLE_THREAD)
+ checkEqualNumberOfLines(expected)
+}
+
+fun List<String>.verifyLinesStartUnordered(vararg expected: String) = verify {
+ val expectedSorted = expected.sorted().toTypedArray()
+ sorted().verifyLinesStart(*expectedSorted)
+}
+
+fun List<String>.verifyExceptions(vararg expected: String) {
+ val original = this
+ val actual = ArrayList<String>().apply {
+ var except = false
+ for (line in original) {
+ when {
+ !except && line.startsWith("\tat") -> except = true
+ except && !line.startsWith("\t") && !line.startsWith("Caused by: ") -> except = false
+ }
+ if (!except) add(line)
+ }
+ }
+ val n = minOf(actual.size, expected.size)
+ for (i in 0 until n) {
+ val exp = sanitize(expected[i], SanitizeMode.FLEXIBLE_THREAD)
+ val act = sanitize(actual[i], SanitizeMode.FLEXIBLE_THREAD)
+ assertEquals("Line ${i + 1}", exp, act)
+ }
+}
+
+
+fun List<String>.verifyLinesStart(vararg expected: String) = verify {
+ val n = minOf(size, expected.size)
+ for (i in 0 until n) {
+ val exp = sanitize(expected[i], SanitizeMode.FLEXIBLE_THREAD)
+ val act = sanitize(get(i), SanitizeMode.FLEXIBLE_THREAD)
+ assertEquals("Line ${i + 1}", exp, act.substring(0, minOf(act.length, exp.length)))
+ }
+ checkEqualNumberOfLines(expected)
+}
+
+private object NullOut : PrintStream(NullOutputStream())
+
+private class NullOutputStream : OutputStream() {
+ override fun write(b: Int) = Unit
+}
+
+private inline fun List<String>.verify(verification: () -> Unit) {
+ try {
+ verification()
+ } catch (t: Throwable) {
+ if (!OUT_ENABLED) {
+ println("Printing [delayed] test output")
+ forEach { println(it) }
+ }
+
+ throw t
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/FastServiceLoaderTest.kt b/kotlinx-coroutines-core/jvm/test/internal/FastServiceLoaderTest.kt
new file mode 100644
index 00000000..a35081e5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/FastServiceLoaderTest.kt
@@ -0,0 +1,17 @@
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Delay
+import kotlin.test.*
+
+class FastServiceLoaderTest {
+ @Test
+ fun testCrossModuleService() {
+ val providers = CoroutineScope::class.java.let { FastServiceLoader.loadProviders(it, it.classLoader) }
+ assertEquals(3, providers.size)
+ val className = "kotlinx.coroutines.android.EmptyCoroutineScopeImpl"
+ for (i in 1 .. 3) {
+ assert(providers[i - 1].javaClass.name == "$className$i")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt
new file mode 100644
index 00000000..af2de24e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016-2019 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.TestBase
+import org.junit.Assert.*
+import org.junit.Test
+import java.util.*
+import java.util.concurrent.atomic.AtomicLong
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * 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 : TestBase() {
+ private val env = LockFreedomTestEnvironment("LockFreeLinkedListAtomicLFStressTest")
+
+ data class IntNode(val i: Int) : LockFreeLinkedListNode()
+
+ private val TEST_DURATION_SEC = 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()
+ val error = AtomicReference<Throwable>()
+
+ @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 = IntNode(threadId)
+ addLastOp(list, node)
+ randomSpinWaitIntermission()
+ tryRemoveOp(node)
+ }
+ 1 -> {
+ // just to test conditional add
+ val list = lists[rnd.nextInt(nLists)]
+ val node = IntNode(threadId)
+ 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 = IntNode(threadId)
+ 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 = IntNode(threadId)
+ val node2 = IntNode(-threadId - 1)
+ 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(TEST_DURATION_SEC) {
+ 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: IntNode) {
+ list.addLast(node)
+ }
+
+ private fun addLastIfTrueOp(list: LockFreeLinkedListHead, node: IntNode) {
+ assertTrue(list.addLastIf(node, { true }))
+ }
+
+ private fun addLastIfFalseOp(list: LockFreeLinkedListHead, node: IntNode) {
+ assertFalse(list.addLastIf(node, { false }))
+ }
+
+ private fun addTwoOp(list1: LockFreeLinkedListHead, node1: IntNode, list2: LockFreeLinkedListHead, node2: IntNode) {
+ val add1 = list1.describeAddLast(node1)
+ val add2 = list2.describeAddLast(node2)
+ val op = object : AtomicOp<Any?>() {
+ 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: IntNode) {
+ 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?>() {
+ 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/internal/LockFreeLinkedListLongStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt
new file mode 100644
index 00000000..dde4b2f6
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.TestBase
+import org.junit.Test
+import java.util.*
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.concurrent.thread
+import kotlin.sequences.buildIterator
+
+/**
+ * This stress test has 2 threads adding on one side on list, 2 more threads adding on the other,
+ * and 6 threads iterating and concurrently removing items. The resulting list that is being
+ * stressed is long.
+ */
+class LockFreeLinkedListLongStressTest : TestBase() {
+ data class IntNode(val i: Int) : LockFreeLinkedListNode()
+ val list = LockFreeLinkedListHead()
+
+ val threads = mutableListOf<Thread>()
+ private val nAdded = 10_000_000 // should not stress more, because that'll run out of memory
+ private val nAddThreads = 4 // must be power of 2 (!!!)
+ private val nRemoveThreads = 6
+ private val removeProbability = 0.2
+ private val workingAdders = AtomicInteger(nAddThreads)
+
+ private fun shallRemove(i: Int) = i and 63 != 42
+
+ @Test
+ fun testStress() {
+ println("--- LockFreeLinkedListLongStressTest")
+ for (j in 0 until nAddThreads)
+ threads += thread(start = false, name = "adder-$j") {
+ for (i in j until nAdded step nAddThreads) {
+ list.addLast(IntNode(i))
+ }
+ println("${Thread.currentThread().name} completed")
+ workingAdders.decrementAndGet()
+ }
+ for (j in 0 until nRemoveThreads)
+ threads += thread(start = false, name = "remover-$j") {
+ val rnd = Random()
+ do {
+ val lastTurn = workingAdders.get() == 0
+ list.forEach<IntNode> { node ->
+ if (shallRemove(node.i) && (lastTurn || rnd.nextDouble() < removeProbability))
+ node.remove()
+ }
+ } while (!lastTurn)
+ println("${Thread.currentThread().name} completed")
+ }
+ println("Starting ${threads.size} threads")
+ for (thread in threads)
+ thread.start()
+ println("Joining threads")
+ for (thread in threads)
+ thread.join()
+ // verification
+ println("Verify result")
+ list.validate()
+ val expected = iterator {
+ for (i in 0 until nAdded)
+ if (!shallRemove(i))
+ yield(i)
+ }
+ list.forEach<IntNode> { node ->
+ require(node.i == expected.next())
+ }
+ require(!expected.hasNext())
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt
new file mode 100644
index 00000000..54932ec1
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Assert.*
+import java.util.*
+import java.util.concurrent.atomic.*
+import kotlin.concurrent.*
+
+/**
+ * This stress test has 6 threads adding randomly first to the list and them immediately undoing
+ * this addition by remove, and 4 threads removing first node. The resulting list that is being
+ * stressed is very short.
+ */
+class LockFreeLinkedListShortStressTest : TestBase() {
+ data class IntNode(val i: Int) : LockFreeLinkedListNode()
+ val list = LockFreeLinkedListHead()
+
+ private val TEST_DURATION = 5000L * stressTestMultiplier
+
+ val threads = mutableListOf<Thread>()
+ private val nAdderThreads = 6
+ private val nRemoverThreads = 4
+ private val completedAdder = AtomicInteger()
+ private val completedRemover = AtomicInteger()
+
+ private val undone = AtomicInteger()
+ private val missed = AtomicInteger()
+ private val removed = AtomicInteger()
+
+ @Test
+ fun testStress() {
+ println("--- LockFreeLinkedListShortStressTest")
+ val deadline = System.currentTimeMillis() + TEST_DURATION
+ repeat(nAdderThreads) { threadId ->
+ threads += thread(start = false, name = "adder-$threadId") {
+ val rnd = Random()
+ while (System.currentTimeMillis() < deadline) {
+ var node: IntNode? = IntNode(threadId)
+ when (rnd.nextInt(3)) {
+ 0 -> list.addLast(node!!)
+ 1 -> assertTrue(list.addLastIf(node!!, { true })) // just to test conditional add
+ 2 -> { // just to test failed conditional add
+ assertFalse(list.addLastIf(node!!, { false }))
+ node = null
+ }
+ }
+ if (node != null) {
+ if (node.remove()) {
+ undone.incrementAndGet()
+ } else {
+ // randomly help other removal's completion
+ if (rnd.nextBoolean()) node.helpRemove()
+ missed.incrementAndGet()
+ }
+ }
+ }
+ completedAdder.incrementAndGet()
+ }
+ }
+ repeat(nRemoverThreads) { threadId ->
+ threads += thread(start = false, name = "remover-$threadId") {
+ while (System.currentTimeMillis() < deadline) {
+ val node = list.removeFirstOrNull()
+ if (node != null) removed.incrementAndGet()
+
+ }
+ completedRemover.incrementAndGet()
+ }
+ }
+ threads.forEach { it.start() }
+ threads.forEach { it.join() }
+ println("Completed successfully ${completedAdder.get()} adder threads")
+ println("Completed successfully ${completedRemover.get()} remover threads")
+ println(" Adders undone ${undone.get()} node additions")
+ println(" Adders missed ${missed.get()} nodes")
+ println("Remover removed ${removed.get()} nodes")
+ assertEquals(nAdderThreads, completedAdder.get())
+ assertEquals(nRemoverThreads, completedRemover.get())
+ assertEquals(missed.get(), removed.get())
+ assertTrue(undone.get() > 0)
+ assertTrue(missed.get() > 0)
+ list.validate()
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt
new file mode 100644
index 00000000..9238681e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import org.junit.Assert.*
+import org.junit.Test
+
+class LockFreeLinkedListTest {
+ data class IntNode(val i: Int) : LockFreeLinkedListNode()
+
+ @Test
+ fun testSimpleAddLast() {
+ val list = LockFreeLinkedListHead()
+ assertContents(list)
+ val n1 = IntNode(1).apply { list.addLast(this) }
+ assertContents(list, 1)
+ val n2 = IntNode(2).apply { list.addLast(this) }
+ assertContents(list, 1, 2)
+ val n3 = IntNode(3).apply { list.addLast(this) }
+ assertContents(list, 1, 2, 3)
+ val n4 = IntNode(4).apply { list.addLast(this) }
+ assertContents(list, 1, 2, 3, 4)
+ assertTrue(n1.remove())
+ assertContents(list, 2, 3, 4)
+ assertTrue(n3.remove())
+ assertContents(list, 2, 4)
+ assertTrue(n4.remove())
+ assertContents(list, 2)
+ assertTrue(n2.remove())
+ assertFalse(n2.remove())
+ assertContents(list)
+ }
+
+ @Test
+ fun testCondOps() {
+ val list = LockFreeLinkedListHead()
+ assertContents(list)
+ assertTrue(list.addLastIf(IntNode(1)) { true })
+ assertContents(list, 1)
+ assertFalse(list.addLastIf(IntNode(2)) { false })
+ assertContents(list, 1)
+ assertTrue(list.addLastIf(IntNode(3)) { true })
+ assertContents(list, 1, 3)
+ assertFalse(list.addLastIf(IntNode(4)) { false })
+ assertContents(list, 1, 3)
+ }
+
+ @Test
+ fun testAtomicOpsSingle() {
+ val list = LockFreeLinkedListHead()
+ assertContents(list)
+ val n1 = IntNode(1).also { single(list.describeAddLast(it)) }
+ assertContents(list, 1)
+ val n2 = IntNode(2).also { single(list.describeAddLast(it)) }
+ assertContents(list, 1, 2)
+ val n3 = IntNode(3).also { single(list.describeAddLast(it)) }
+ assertContents(list, 1, 2, 3)
+ val n4 = IntNode(4).also { single(list.describeAddLast(it)) }
+ assertContents(list, 1, 2, 3, 4)
+ }
+
+ private fun single(part: AtomicDesc) {
+ val operation = object : AtomicOp<Any?>() {
+ override fun prepare(affected: Any?): Any? = part.prepare(this)
+ override fun complete(affected: Any?, failure: Any?) = part.complete(this, failure)
+ }
+ assertTrue(operation.perform(null) == null)
+ }
+
+ private fun assertContents(list: LockFreeLinkedListHead, vararg expected: Int) {
+ list.validate()
+ val n = expected.size
+ val actual = IntArray(n)
+ var index = 0
+ list.forEach<IntNode> { actual[index++] = it.i }
+ assertEquals(n, index)
+ for (i in 0 until n) assertEquals("item i", expected[i], actual[i])
+ assertEquals(expected.isEmpty(), list.isEmpty)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueStressTest.kt
new file mode 100644
index 00000000..16e436ef
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueStressTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016-2018 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 org.junit.runner.*
+import org.junit.runners.*
+import java.util.concurrent.*
+import kotlin.concurrent.*
+import kotlin.test.*
+
+// Tests many short queues to stress copy/resize
+@RunWith(Parameterized::class)
+class LockFreeTaskQueueStressTest(
+ private val nConsumers: Int
+) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "nConsumers={0}")
+ @JvmStatic
+ fun params(): Collection<Int> = listOf(1, 3)
+ }
+
+ private val singleConsumer = nConsumers == 1
+
+ private val nSeconds = 3 * stressTestMultiplier
+ private val nProducers = 4
+ private val batchSize = 100
+
+ private val batch = atomic(0)
+ private val produced = atomic(0L)
+ private val consumed = atomic(0L)
+ private var expected = LongArray(nProducers)
+
+ private val queue = atomic<LockFreeTaskQueue<Item>?>(null)
+ private val done = atomic(0)
+ private val doneProducers = atomic(0)
+
+ private val barrier = CyclicBarrier(nProducers + nConsumers + 1)
+
+ private class Item(val producer: Int, val index: Long)
+
+ @Test
+ fun testStress() {
+ val threads = mutableListOf<Thread>()
+ threads += thread(name = "Pacer", start = false) {
+ while (done.value == 0) {
+ queue.value = LockFreeTaskQueue(singleConsumer)
+ batch.value = 0
+ doneProducers.value = 0
+ barrier.await() // start consumers & producers
+ barrier.await() // await consumers & producers
+ }
+ queue.value = null
+ println("Pacer done")
+ barrier.await() // wakeup the rest
+ }
+ threads += List(nConsumers) { consumer ->
+ thread(name = "Consumer-$consumer", start = false) {
+ while (true) {
+ barrier.await()
+ val queue = queue.value ?: break
+ while (true) {
+ val item = queue.removeFirstOrNull()
+ if (item == null) {
+ if (doneProducers.value == nProducers && queue.isEmpty) break // that's it
+ continue // spin to retry
+ }
+ consumed.incrementAndGet()
+ if (singleConsumer) {
+ // This check only properly works in single-consumer case
+ val eItem = expected[item.producer]++
+ if (eItem != item.index) error("Expected $eItem but got ${item.index} from Producer-${item.producer}")
+ }
+ }
+ barrier.await()
+ }
+ println("Consumer-$consumer done")
+ }
+ }
+ threads += List(nProducers) { producer ->
+ thread(name = "Producer-$producer", start = false) {
+ var index = 0L
+ while (true) {
+ barrier.await()
+ val queue = queue.value ?: break
+ while (true) {
+ if (batch.incrementAndGet() >= batchSize) break
+ check(queue.addLast(Item(producer, index++))) // never closed
+ produced.incrementAndGet()
+ }
+ doneProducers.incrementAndGet()
+ barrier.await()
+ }
+ println("Producer-$producer done")
+ }
+ }
+ threads.forEach {
+ it.setUncaughtExceptionHandler { t, e ->
+ System.err.println("Thread $t failed: $e")
+ e.printStackTrace()
+ done.value = 1
+ error("Thread $t failed", e)
+ }
+ }
+ threads.forEach { it.start() }
+ for (second in 1..nSeconds) {
+ Thread.sleep(1000)
+ println("$second: produced=${produced.value}, consumed=${consumed.value}")
+ if (done.value == 1) break
+ }
+ done.value = 1
+ threads.forEach { it.join() }
+ println("T: produced=${produced.value}, consumed=${consumed.value}")
+ assertEquals(produced.value, consumed.value)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueTest.kt
new file mode 100644
index 00000000..29f019bd
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import org.junit.runner.*
+import org.junit.runners.*
+import kotlin.test.*
+
+@RunWith(Parameterized::class)
+class LockFreeTaskQueueTest(
+ private val singleConsumer: Boolean
+) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "singleConsumer={0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = listOf(
+ arrayOf<Any>(false),
+ arrayOf<Any>(true)
+ )
+ }
+
+ @Test
+ fun testBasic() {
+ val q = LockFreeTaskQueue<Int>(singleConsumer)
+ assertTrue(q.isEmpty)
+ assertEquals(0, q.size)
+ assertTrue(q.addLast(1))
+ assertFalse(q.isEmpty)
+ assertEquals(1, q.size)
+ assertTrue(q.addLast(2))
+ assertFalse(q.isEmpty)
+ assertEquals(2, q.size)
+ assertTrue(q.addLast(3))
+ assertFalse(q.isEmpty)
+ assertEquals(3, q.size)
+ assertEquals(1, q.removeFirstOrNull())
+ assertFalse(q.isEmpty)
+ assertEquals(2, q.size)
+ assertEquals(2, q.removeFirstOrNull())
+ assertFalse(q.isEmpty)
+ assertEquals(1, q.size)
+ assertTrue(q.addLast(4))
+ assertFalse(q.isEmpty)
+ assertEquals(2, q.size)
+ q.close()
+ assertFalse(q.isEmpty)
+ assertEquals(2, q.size)
+ assertFalse(q.addLast(5))
+ assertFalse(q.isEmpty)
+ assertEquals(2, q.size)
+ assertEquals(3, q.removeFirstOrNull())
+ assertFalse(q.isEmpty)
+ assertEquals(1, q.size)
+ assertEquals(4, q.removeFirstOrNull())
+ assertTrue(q.isEmpty)
+ assertEquals(0, q.size)
+ }
+
+ @Test
+ fun testCopyGrow() {
+ val n = 1000 * stressTestMultiplier
+ val q = LockFreeTaskQueue<Int>(singleConsumer)
+ assertTrue(q.isEmpty)
+ repeat(n) { i ->
+ assertTrue(q.addLast(i))
+ assertFalse(q.isEmpty)
+ }
+ repeat(n) { i ->
+ assertEquals(i, q.removeFirstOrNull())
+ }
+ assertTrue(q.isEmpty)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/SegmentBasedQueue.kt b/kotlinx-coroutines-core/jvm/test/internal/SegmentBasedQueue.kt
new file mode 100644
index 00000000..293be7a5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/SegmentBasedQueue.kt
@@ -0,0 +1,72 @@
+package kotlinx.coroutines.internal
+
+import kotlinx.atomicfu.atomic
+
+/**
+ * This queue implementation is based on [SegmentQueue] for testing purposes and is organized as follows. Essentially,
+ * the [SegmentBasedQueue] is represented as an infinite array of segments, each stores one element (see [OneElementSegment]).
+ * Both [enqueue] and [dequeue] operations increment the corresponding global index ([enqIdx] for [enqueue] and
+ * [deqIdx] for [dequeue]) and work with the indexed by this counter cell. Since both operations increment the indices
+ * at first, there could be a race: [enqueue] increments [enqIdx], then [dequeue] checks that the queue is not empty
+ * (that's true) and increments [deqIdx], looking into the corresponding cell after that; however, the cell is empty
+ * because the [enqIdx] operation has not been put its element yet. To make the queue non-blocking, [dequeue] can mark
+ * the cell with [BROKEN] token and retry the operation, [enqueue] at the same time should restart as well; this way,
+ * the queue is obstruction-free.
+ */
+internal class SegmentBasedQueue<T> : SegmentQueue<OneElementSegment<T>>() {
+ override fun newSegment(id: Long, prev: OneElementSegment<T>?): OneElementSegment<T> = OneElementSegment(id, prev)
+
+ private val enqIdx = atomic(0L)
+ private val deqIdx = atomic(0L)
+
+ // Returns the segments associated with the enqueued element.
+ fun enqueue(element: T): OneElementSegment<T> {
+ while (true) {
+ var tail = this.tail
+ val enqIdx = this.enqIdx.getAndIncrement()
+ tail = getSegment(tail, enqIdx) ?: continue
+ if (tail.element.value === BROKEN) continue
+ if (tail.element.compareAndSet(null, element)) return tail
+ }
+ }
+
+ fun dequeue(): T? {
+ while (true) {
+ if (this.deqIdx.value >= this.enqIdx.value) return null
+ var firstSegment = this.head
+ val deqIdx = this.deqIdx.getAndIncrement()
+ firstSegment = getSegmentAndMoveHead(firstSegment, deqIdx) ?: continue
+ var el = firstSegment.element.value
+ if (el === null) {
+ if (firstSegment.element.compareAndSet(null, BROKEN)) continue
+ else el = firstSegment.element.value
+ }
+ if (el === REMOVED) continue
+ return el as T
+ }
+ }
+
+ val numberOfSegments: Int get() {
+ var s: OneElementSegment<T>? = head
+ var i = 0
+ while (s != null) {
+ s = s.next
+ i++
+ }
+ return i
+ }
+}
+
+internal class OneElementSegment<T>(id: Long, prev: OneElementSegment<T>?) : Segment<OneElementSegment<T>>(id, prev) {
+ val element = atomic<Any?>(null)
+
+ override val removed get() = element.value === REMOVED
+
+ fun removeSegment() {
+ element.value = REMOVED
+ remove()
+ }
+}
+
+private val BROKEN = Symbol("BROKEN")
+private val REMOVED = Symbol("REMOVED") \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueLCStressTest.kt
new file mode 100644
index 00000000..c8493f6f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueLCStressTest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import com.devexperts.dxlab.lincheck.LinChecker
+import com.devexperts.dxlab.lincheck.annotations.Operation
+import com.devexperts.dxlab.lincheck.annotations.Param
+import com.devexperts.dxlab.lincheck.paramgen.IntGen
+import com.devexperts.dxlab.lincheck.strategy.stress.StressCTest
+import org.junit.Test
+
+@StressCTest
+class SegmentQueueLCStressTest {
+ private val q = SegmentBasedQueue<Int>()
+
+ @Operation
+ fun add(@Param(gen = IntGen::class) x: Int) {
+ q.enqueue(x)
+ }
+
+ @Operation
+ fun poll(): Int? = q.dequeue()
+
+ @Test
+ fun test() {
+ LinChecker.check(SegmentQueueLCStressTest::class.java)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt b/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt
new file mode 100644
index 00000000..9a6ee42a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt
@@ -0,0 +1,99 @@
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.TestBase
+import org.junit.Test
+import java.util.*
+import java.util.concurrent.CyclicBarrier
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.concurrent.thread
+import kotlin.random.Random
+import kotlin.test.assertEquals
+
+class SegmentQueueTest : TestBase() {
+
+ @Test
+ fun simpleTest() {
+ val q = SegmentBasedQueue<Int>()
+ assertEquals( 1, q.numberOfSegments)
+ assertEquals(null, q.dequeue())
+ q.enqueue(1)
+ assertEquals(1, q.numberOfSegments)
+ q.enqueue(2)
+ assertEquals(2, q.numberOfSegments)
+ assertEquals(1, q.dequeue())
+ assertEquals(2, q.numberOfSegments)
+ assertEquals(2, q.dequeue())
+ assertEquals(1, q.numberOfSegments)
+ assertEquals(null, q.dequeue())
+ }
+
+ @Test
+ fun testSegmentRemoving() {
+ val q = SegmentBasedQueue<Int>()
+ q.enqueue(1)
+ val s = q.enqueue(2)
+ q.enqueue(3)
+ assertEquals(3, q.numberOfSegments)
+ s.removeSegment()
+ assertEquals(2, q.numberOfSegments)
+ assertEquals(1, q.dequeue())
+ assertEquals(3, q.dequeue())
+ assertEquals(null, q.dequeue())
+ }
+
+ @Test
+ fun testRemoveHeadSegment() {
+ val q = SegmentBasedQueue<Int>()
+ q.enqueue(1)
+ val s = q.enqueue(2)
+ assertEquals(1, q.dequeue())
+ q.enqueue(3)
+ s.removeSegment()
+ assertEquals(3, q.dequeue())
+ assertEquals(null, q.dequeue())
+ }
+
+ @Test
+ fun stressTest() {
+ val q = SegmentBasedQueue<Int>()
+ val expectedQueue = ArrayDeque<Int>()
+ val r = Random(0)
+ repeat(1_000_000 * stressTestMultiplier) {
+ if (r.nextBoolean()) { // add
+ val el = r.nextInt()
+ q.enqueue(el)
+ expectedQueue.add(el)
+ } else { // remove
+ assertEquals(expectedQueue.poll(), q.dequeue())
+ }
+ }
+ }
+
+ @Test
+ fun stressTestRemoveSegmentsSerial() = stressTestRemoveSegments(false)
+
+ @Test
+ fun stressTestRemoveSegmentsRandom() = stressTestRemoveSegments(true)
+
+ private fun stressTestRemoveSegments(random: Boolean) {
+ val N = 100_000 * stressTestMultiplier
+ val T = 10
+ val q = SegmentBasedQueue<Int>()
+ val segments = (1..N).map { q.enqueue(it) }.toMutableList()
+ if (random) segments.shuffle()
+ assertEquals(N, q.numberOfSegments)
+ val nextSegmentIndex = AtomicInteger()
+ val barrier = CyclicBarrier(T)
+ (1..T).map {
+ thread {
+ barrier.await()
+ while (true) {
+ val i = nextSegmentIndex.getAndIncrement()
+ if (i >= N) break
+ segments[i].removeSegment()
+ }
+ }
+ }.forEach { it.join() }
+ assertEquals(2, q.numberOfSegments)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt b/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt
new file mode 100644
index 00000000..12b35405
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+import java.util.*
+
+class ThreadSafeHeapTest : TestBase() {
+ internal class Node(val value: Int) : ThreadSafeHeapNode, Comparable<Node> {
+ override var heap: ThreadSafeHeap<*>? = null
+ override var index = -1
+ override fun compareTo(other: Node): Int = value.compareTo(other.value)
+ override fun equals(other: Any?): Boolean = other is Node && other.value == value
+ override fun hashCode(): Int = value
+ override fun toString(): String = "$value"
+ }
+
+ @Test
+ fun testBasic() {
+ val h = ThreadSafeHeap<Node>()
+ assertEquals(null, h.peek())
+ val n1 = Node(1)
+ h.addLast(n1)
+ assertEquals(n1, h.peek())
+ val n2 = Node(2)
+ h.addLast(n2)
+ assertEquals(n1, h.peek())
+ val n3 = Node(3)
+ h.addLast(n3)
+ assertEquals(n1, h.peek())
+ val n4 = Node(4)
+ h.addLast(n4)
+ assertEquals(n1, h.peek())
+ val n5 = Node(5)
+ h.addLast(n5)
+ assertEquals(n1, h.peek())
+ assertEquals(n1, h.removeFirstOrNull())
+ assertEquals(-1, n1.index)
+ assertEquals(n2, h.peek())
+ h.remove(n2)
+ assertEquals(n3, h.peek())
+ h.remove(n4)
+ assertEquals(n3, h.peek())
+ h.remove(n3)
+ assertEquals(n5, h.peek())
+ h.remove(n5)
+ assertEquals(null, h.peek())
+ }
+
+ @Test
+ fun testRandomSort() {
+ val n = 1000 * stressTestMultiplier
+ val r = Random(1)
+ val h = ThreadSafeHeap<Node>()
+ val a = IntArray(n) { r.nextInt() }
+ repeat(n) { h.addLast(Node(a[it])) }
+ a.sort()
+ repeat(n) { assertEquals(Node(a[it]), h.removeFirstOrNull()) }
+ assertEquals(null, h.peek())
+ }
+
+ @Test
+ fun testRandomRemove() {
+ val n = 1000 * stressTestMultiplier
+ check(n % 2 == 0) { "Must be even" }
+ val r = Random(1)
+ val h = ThreadSafeHeap<Node>()
+ val set = TreeSet<Node>()
+ repeat(n) {
+ val node = Node(r.nextInt())
+ h.addLast(node)
+ assertTrue(set.add(node))
+ }
+ while (!h.isEmpty) {
+ // pick random node to remove
+ val rndNode: Node
+ while (true) {
+ val tail = set.tailSet(Node(r.nextInt()))
+ if (!tail.isEmpty()) {
+ rndNode = tail.first()
+ break
+ }
+ }
+ assertTrue(set.remove(rndNode))
+ assertTrue(h.remove(rndNode))
+ // remove head and validate
+ val headNode = h.removeFirstOrNull()!! // must not be null!!!
+ assertSame(headNode, set.first(), "Expected ${set.first()}, but found $headNode, remaining size ${h.size}")
+ assertTrue(set.remove(headNode))
+ assertEquals(set.size, h.size)
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/ChannelIsClosedLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/ChannelIsClosedLCStressTest.kt
new file mode 100644
index 00000000..44ba182d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/linearizability/ChannelIsClosedLCStressTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-2019 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 com.devexperts.dxlab.lincheck.*
+import com.devexperts.dxlab.lincheck.annotations.*
+import com.devexperts.dxlab.lincheck.paramgen.*
+import com.devexperts.dxlab.lincheck.strategy.stress.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.junit.*
+import java.io.*
+
+@Param(name = "value", gen = IntGen::class, conf = "1:3")
+class ChannelIsClosedLCStressTest : TestBase() {
+
+ private val lt = LinTesting()
+ private val channel = Channel<Int>()
+
+ @Operation(runOnce = true)
+ fun send1(@Param(name = "value") value: Int) = lt.run("send1") { channel.send(value) }
+
+ @Operation(runOnce = true)
+ fun send2(@Param(name = "value") value: Int) = lt.run("send2") { channel.send(value) }
+
+ @Operation(runOnce = true)
+ fun receive1() = lt.run("receive1") { channel.receive() }
+
+ @Operation(runOnce = true)
+ fun receive2() = lt.run("receive2") { channel.receive() }
+
+ @Operation(runOnce = true)
+ fun close1() = lt.run("close1") { channel.close(IOException("close1")) }
+
+ @Operation(runOnce = true)
+ fun isClosedForReceive() = lt.run("isClosedForReceive") { channel.isClosedForReceive }
+
+ @Operation(runOnce = true)
+ fun isClosedForSend() = lt.run("isClosedForSend") { channel.isClosedForSend }
+
+ @Test
+ fun testLinearizability() {
+ val options = StressOptions()
+ .iterations(100 * stressTestMultiplierSqrt)
+ .invocationsPerIteration(1000 * stressTestMultiplierSqrt)
+ .threads(3)
+ .verifier(LinVerifier::class.java)
+
+ LinChecker.check(ChannelIsClosedLCStressTest::class.java, options)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/ChannelLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/ChannelLCStressTest.kt
new file mode 100644
index 00000000..f4b77563
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/linearizability/ChannelLCStressTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016-2019 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 com.devexperts.dxlab.lincheck.*
+import com.devexperts.dxlab.lincheck.annotations.*
+import com.devexperts.dxlab.lincheck.paramgen.*
+import com.devexperts.dxlab.lincheck.strategy.stress.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.junit.*
+import java.io.*
+
+@Param(name = "value", gen = IntGen::class, conf = "1:3")
+class ChannelLCStressTest : TestBase() {
+
+ private companion object {
+ // Emulating ctor argument for lincheck
+ var capacity = 0
+ }
+
+ private val lt = LinTesting()
+ private var channel: Channel<Int> = Channel(capacity)
+
+ @Operation(runOnce = true)
+ fun send1(@Param(name = "value") value: Int) = lt.run("send1") { channel.send(value) }
+
+ @Operation(runOnce = true)
+ fun send2(@Param(name = "value") value: Int) = lt.run("send2") { channel.send(value) }
+
+ @Operation(runOnce = true)
+ fun receive1() = lt.run("receive1") { channel.receive() }
+
+ @Operation(runOnce = true)
+ fun receive2() = lt.run("receive2") { channel.receive() }
+
+ @Operation(runOnce = true)
+ fun close1() = lt.run("close1") { channel.close(IOException("close1")) }
+
+ @Operation(runOnce = true)
+ fun close2() = lt.run("close2") { channel.close(IOException("close2")) }
+
+ @Test
+ fun testRendezvousChannelLinearizability() {
+ runTest(0)
+ }
+
+ @Test
+ fun testArrayChannelLinearizability() {
+ for (i in listOf(1, 2, 16)) {
+ runTest(i)
+ }
+ }
+
+ @Test
+ fun testConflatedChannelLinearizability() = runTest(Channel.CONFLATED)
+
+ @Test
+ fun testUnlimitedChannelLinearizability() = runTest(Channel.UNLIMITED)
+
+ private fun runTest(capacity: Int) {
+ ChannelLCStressTest.capacity = capacity
+ val options = StressOptions()
+ .iterations(50 * stressTestMultiplierSqrt)
+ .invocationsPerIteration(500 * stressTestMultiplierSqrt)
+ .threads(3)
+ .verifier(LinVerifier::class.java)
+ LinChecker.check(ChannelLCStressTest::class.java, options)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/LinTesting.kt b/kotlinx-coroutines-core/jvm/test/linearizability/LinTesting.kt
new file mode 100644
index 00000000..14cf2a70
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/linearizability/LinTesting.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import com.devexperts.dxlab.lincheck.Actor
+import com.devexperts.dxlab.lincheck.Result
+import com.devexperts.dxlab.lincheck.Utils.*
+import com.devexperts.dxlab.lincheck.execution.*
+import com.devexperts.dxlab.lincheck.verifier.Verifier
+import java.lang.reflect.Method
+import java.util.*
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
+
+data class OpResult(val name: String, val value: Any?) {
+ override fun toString(): String = "$name=$value"
+}
+
+private const val CS_STR = "COROUTINE_SUSPENDED"
+
+class LinTesting {
+ private val resumed = object : ThreadLocal<ArrayList<OpResult>>() {
+ override fun initialValue() = arrayListOf<OpResult>()
+ }
+
+ private inline fun wrap(block: () -> Any?): Any? =
+ try { repr(block()) }
+ catch(e: Throwable) { repr(e) }
+
+ private fun repr(e: Any?): Any? =
+ when {
+ e === COROUTINE_SUSPENDED -> CS_STR
+ e is Throwable -> e.toString()
+ else -> e
+ }
+
+ fun <T> run(name: String, block: suspend () -> T): List<OpResult> {
+ val list = resumed.get()
+ list.clear()
+ val result = arrayListOf(OpResult(name, wrap {
+ block.startCoroutineUninterceptedOrReturn(completion = object : Continuation<Any?> {
+ override val context: CoroutineContext
+ get() = EmptyCoroutineContext
+
+ override fun resumeWith(result: kotlin.Result<Any?>) {
+ val value = if (result.isSuccess) result.getOrNull() else result.exceptionOrNull()
+ resumed.get() += OpResult(name, repr(value))
+ }
+ }
+ )
+ }))
+ result.addAll(list)
+ return result
+ }
+}
+
+class LinVerifier(scenario: ExecutionScenario,
+ testClass: Class<*>) : Verifier {
+ private val possibleResultsSet: Set<List<List<Result>>> =
+ generateAllLinearizableExecutions(scenario.parallelExecution)
+ .asSequence()
+ .map { linEx: List<Actor> ->
+ val res: List<Result> = executeActors(testClass.newInstance(), linEx)
+ val actorIds = linEx.asSequence().withIndex().associateBy({ it.value}, { it.index })
+ scenario.parallelExecution.map { actors -> actors.map { actor -> res[actorIds[actor]!!] } }
+ }.toSet()
+
+ override fun verifyResults(results: ExecutionResult): Boolean {
+ if (!valid(results.parallelResults)) {
+ println("\nNon-linearizable execution:")
+ printResults(results.parallelResults)
+ println("\nPossible linearizable executions:")
+ possibleResultsSet.forEach { possibleResults ->
+ printResults(possibleResults)
+ println()
+ }
+ throw AssertionError("Non-linearizable execution detected, see log for details")
+ }
+
+ return true
+ }
+
+ private fun printResults(results: List<List<Result>>) {
+ results.forEachIndexed { index, res ->
+ println("Thread $index: $res")
+ }
+ println("Op map: ${results.toOpMap()}")
+ }
+
+ private fun valid(results: List<List<Result>>): Boolean =
+ (results in possibleResultsSet) || possibleResultsSet.any { matches(results, it) }
+
+ private fun matches(results: List<List<Result>>, possible: List<List<Result>>): Boolean =
+ results.toOpMap() == possible.toOpMap()
+
+ private fun List<List<Result>>.toOpMap(): Map<String, List<Any?>> {
+ val filtered = flatMap { it }.flatMap { it.resultValue }.filter { it.value != CS_STR }
+ return filtered.groupBy({ it.name }, { it.value })
+ }
+
+ private fun generateAllLinearizableExecutions(actorsPerThread: List<List<Actor>>): List<List<Actor>> {
+ val executions = ArrayList<List<Actor>>()
+ generateLinearizableExecutions0(
+ executions, actorsPerThread, ArrayList(), IntArray(actorsPerThread.size),
+ actorsPerThread.sumBy { it.size })
+ return executions
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun generateLinearizableExecutions0(executions: MutableList<List<Actor>>, actorsPerThread: List<List<Actor>>,
+ currentExecution: ArrayList<Actor>, indexes: IntArray, length: Int) {
+ if (currentExecution.size == length) {
+ executions.add(currentExecution.clone() as List<Actor>)
+ return
+ }
+ for (i in indexes.indices) {
+ val actors = actorsPerThread[i]
+ if (indexes[i] == actors.size)
+ continue
+ currentExecution.add(actors[indexes[i]])
+ indexes[i]++
+ generateLinearizableExecutions0(executions, actorsPerThread, currentExecution, indexes, length)
+ indexes[i]--
+ currentExecution.removeAt(currentExecution.size - 1)
+ }
+ }
+}
+
+private val VALUE = Result::class.java.getDeclaredField("value").apply { isAccessible = true }
+
+@Suppress("UNCHECKED_CAST")
+private val Result.resultValue: List<OpResult>
+ get() = VALUE.get(this) as List<OpResult>
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLCStressTest.kt
new file mode 100644
index 00000000..54661548
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLCStressTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016-2019 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 com.devexperts.dxlab.lincheck.*
+import com.devexperts.dxlab.lincheck.annotations.*
+import com.devexperts.dxlab.lincheck.paramgen.*
+import com.devexperts.dxlab.lincheck.strategy.stress.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlin.test.*
+
+@Param(name = "value", gen = IntGen::class, conf = "1:3")
+class LockFreeListLCStressTest : TestBase() {
+ class Node(val value: Int): LockFreeLinkedListNode()
+
+ private val q: LockFreeLinkedListHead = LockFreeLinkedListHead()
+
+ @Operation
+ fun addLast(@Param(name = "value") value: Int) {
+ q.addLast(Node(value))
+ }
+
+ @Operation
+ fun addLastIfNotSame(@Param(name = "value") value: Int) {
+ q.addLastIfPrev(Node(value)) { !it.isSame(value) }
+ }
+
+ @Operation
+ fun removeFirst(): Int? {
+ val node = q.removeFirstOrNull() ?: return null
+ return (node as Node).value
+ }
+
+ @Operation
+ fun removeFirstOrPeekIfNotSame(@Param(name = "value") value: Int): Int? {
+ val node = q.removeFirstIfIsInstanceOfOrPeekIf<Node> { !it.isSame(value) } ?: return null
+ return node.value
+ }
+
+ private fun Any.isSame(value: Int) = this is Node && this.value == value
+
+ @Test
+ fun testAddRemoveLinearizability() {
+ val options = StressOptions()
+ .iterations(100 * stressTestMultiplierSqrt)
+ .invocationsPerIteration(1000 * stressTestMultiplierSqrt)
+ .threads(3)
+ LinChecker.check(LockFreeListLCStressTest::class.java, options)
+ }
+
+ private var _curElements: ArrayList<Int>? = null
+ private val curElements: ArrayList<Int> get() {
+ if (_curElements == null) {
+ _curElements = ArrayList()
+ q.forEach<Node> { _curElements!!.add(it.value) }
+ }
+ return _curElements!!
+ }
+
+ override fun equals(other: Any?): Boolean {
+ other as LockFreeListLCStressTest
+ return curElements == other.curElements
+ }
+
+ override fun hashCode(): Int {
+ return curElements.hashCode()
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLCStressTest.kt
new file mode 100644
index 00000000..ea2afa10
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLCStressTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016-2019 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 com.devexperts.dxlab.lincheck.*
+import com.devexperts.dxlab.lincheck.annotations.*
+import com.devexperts.dxlab.lincheck.paramgen.*
+import com.devexperts.dxlab.lincheck.strategy.stress.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlin.test.*
+
+internal data class Snapshot(val elements: List<Int>, val isClosed: Boolean) {
+ constructor(q: LockFreeTaskQueue<Int>) : this(q.map { it }, q.isClosed())
+}
+
+@OpGroupConfig.OpGroupConfigs(OpGroupConfig(name = "consumer", nonParallel = true))
+@Param(name = "value", gen = IntGen::class, conf = "1:3")
+class SCLockFreeTaskQueueLCStressTest : LockFreeTaskQueueLCTestBase() {
+ private val q: LockFreeTaskQueue<Int> = LockFreeTaskQueue(singleConsumer = true)
+
+ @Operation
+ fun close() = q.close()
+
+ @Operation
+ fun addLast(@Param(name = "value") value: Int) = q.addLast(value)
+
+ /**
+ * Note that removeFirstOrNull is not linearizable w.r.t. to addLast, so here
+ * we test only linearizability of close.
+ */
+// @Operation(group = "consumer")
+// fun removeFirstOrNull() = q.removeFirstOrNull()
+
+ @Test
+ fun testSC() = linTest()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as SCLockFreeTaskQueueLCStressTest
+
+ return Snapshot(q) == Snapshot(other.q)
+ }
+
+ override fun hashCode(): Int = Snapshot(q).hashCode()
+}
+
+@Param(name = "value", gen = IntGen::class, conf = "1:3")
+class MCLockFreeTaskQueueLCStressTest : LockFreeTaskQueueLCTestBase() {
+ private val q: LockFreeTaskQueue<Int> = LockFreeTaskQueue(singleConsumer = false)
+
+ @Operation
+ fun close() = q.close()
+
+ @Operation
+ fun addLast(@Param(name = "value") value: Int) = q.addLast(value)
+
+ /**
+ * Note that removeFirstOrNull is not linearizable w.r.t. to addLast, so here
+ * we test only linearizability of close.
+ */
+// @Operation(group = "consumer")
+// fun removeFirstOrNull() = q.removeFirstOrNull()
+
+ @Test
+ fun testMC() = linTest()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as MCLockFreeTaskQueueLCStressTest
+
+ return Snapshot(q) == Snapshot(other.q)
+ }
+
+ override fun hashCode(): Int = Snapshot(q).hashCode()
+}
+
+open class LockFreeTaskQueueLCTestBase : TestBase() {
+ fun linTest() {
+ val options = StressOptions()
+ .iterations(100 * stressTestMultiplierSqrt)
+ .invocationsPerIteration(1000 * stressTestMultiplierSqrt)
+ .threads(2)
+ LinChecker.check(this::class.java, options)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherRaceStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherRaceStressTest.kt
new file mode 100644
index 00000000..2b5a8968
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherRaceStressTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.util.concurrent.atomic.*
+
+class BlockingCoroutineDispatcherRaceStressTest : SchedulerTestBase() {
+ private val concurrentWorkers = AtomicInteger(0)
+
+ @Before
+ fun setUp() {
+ // In case of starvation test will hang
+ idleWorkerKeepAliveNs = Long.MAX_VALUE
+ }
+
+ @Test
+ fun testAddPollRace() = runBlocking {
+ val limitingDispatcher = blockingDispatcher(1)
+ val iterations = 25_000 * stressTestMultiplier
+ // Stress test for specific case (race #2 from LimitingDispatcher). Shouldn't hang.
+ for (i in 1..iterations) {
+ val tasks = (1..2).map {
+ async(limitingDispatcher) {
+ try {
+ val currentlyExecuting = concurrentWorkers.incrementAndGet()
+ require(currentlyExecuting == 1)
+ } finally {
+ concurrentWorkers.decrementAndGet()
+ }
+ }
+ }
+
+ tasks.forEach { it.await() }
+ }
+
+ checkPoolThreadsCreated(2..4)
+ }
+
+ @Test
+ fun testPingPongThreadsCount() = runBlocking {
+ corePoolSize = CORES_COUNT
+ val iterations = 100_000 * stressTestMultiplier
+ // Stress test for specific case (race #2 from LimitingDispatcher). Shouldn't hang.
+ for (i in 1..iterations) {
+ val tasks = (1..2).map {
+ async(dispatcher) {
+ // Useless work
+ concurrentWorkers.incrementAndGet()
+ concurrentWorkers.decrementAndGet()
+ }
+ }
+
+ tasks.forEach { it.await() }
+ }
+
+ checkPoolThreadsCreated(CORES_COUNT)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherStressTest.kt
new file mode 100644
index 00000000..08b4914c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherStressTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DeferredResultUnused")
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+
+class BlockingCoroutineDispatcherStressTest : SchedulerTestBase() {
+
+ init {
+ corePoolSize = CORES_COUNT
+ }
+
+ private val observedConcurrency = ConcurrentHashMap<Int, Boolean>()
+ private val concurrentWorkers = AtomicInteger(0)
+
+ @Test
+ fun testLimitParallelism() = runBlocking {
+ val limitingDispatcher = blockingDispatcher(CORES_COUNT)
+ val iterations = 50_000 * stressTestMultiplier
+ val tasks = (1..iterations).map {
+ async(limitingDispatcher) {
+ try {
+ val currentlyExecuting = concurrentWorkers.incrementAndGet()
+ observedConcurrency[currentlyExecuting] = true
+ require(currentlyExecuting <= CORES_COUNT)
+ } finally {
+ concurrentWorkers.decrementAndGet()
+ }
+ }
+ }
+
+ tasks.forEach { it.await() }
+ require(tasks.isNotEmpty())
+ for (i in CORES_COUNT + 1..CORES_COUNT * 2) {
+ require(i !in observedConcurrency.keys) { "Unexpected state: $observedConcurrency" }
+ }
+
+ checkPoolThreadsCreated(CORES_COUNT..CORES_COUNT + CORES_COUNT * 2)
+ }
+
+ @Test
+ fun testCpuTasksStarvation() = runBlocking {
+ val iterations = 1000 * stressTestMultiplier
+
+ repeat(iterations) {
+ // Create a dispatcher every iteration to increase probability of race
+ val dispatcher = ExperimentalCoroutineDispatcher(CORES_COUNT)
+ val blockingDispatcher = dispatcher.blocking(100)
+
+ val blockingBarrier = CyclicBarrier(CORES_COUNT * 3 + 1)
+ val cpuBarrier = CyclicBarrier(CORES_COUNT + 1)
+
+ val cpuTasks = CopyOnWriteArrayList<Deferred<*>>()
+ val blockingTasks = CopyOnWriteArrayList<Deferred<*>>()
+
+ repeat(CORES_COUNT) {
+ async(dispatcher) {
+ // These two will be stolen first
+ blockingTasks += async(blockingDispatcher) { blockingBarrier.await() }
+ blockingTasks += async(blockingDispatcher) { blockingBarrier.await() }
+
+
+ // Empty on CPU job which should be executed while blocked tasks are hang
+ cpuTasks += async(dispatcher) { cpuBarrier.await() }
+
+ // Block with next task. Block cores * 3 threads in total
+ blockingTasks += async(blockingDispatcher) { blockingBarrier.await() }
+ }
+ }
+
+ cpuTasks.forEach { require(it.isActive) }
+ cpuBarrier.await()
+ cpuTasks.forEach { it.await() }
+ blockingTasks.forEach { require(it.isActive) }
+ blockingBarrier.await()
+ blockingTasks.forEach { it.await() }
+ dispatcher.close()
+ }
+ }
+
+ @Test
+ fun testBlockingTasksStarvation() = runBlocking {
+ corePoolSize = 2 // Easier to reproduce race with unparks
+ val iterations = 10_000 * stressTestMultiplier
+ val blockingLimit = 4 // CORES_COUNT * 3
+ val blocking = blockingDispatcher(blockingLimit)
+
+ repeat(iterations) {
+ val barrier = CyclicBarrier(blockingLimit + 1)
+ // Should eat all limit * 3 cpu without any starvation
+ val tasks = (1..blockingLimit).map { async(blocking) { barrier.await() } }
+
+ tasks.forEach { require(it.isActive) }
+ barrier.await()
+ tasks.joinAll()
+ }
+ }
+
+ @Test
+ fun testBlockingTasksStarvationWithCpuTasks() = runBlocking {
+ val iterations = 1000 * stressTestMultiplier
+ val blockingLimit = CORES_COUNT * 2
+ val blocking = blockingDispatcher(blockingLimit)
+
+ repeat(iterations) {
+ // Overwhelm global queue with external CPU tasks
+ val cpuTasks = (1..CORES_COUNT).map { async(dispatcher) { while (true) delay(1) } }
+
+ val barrier = CyclicBarrier(blockingLimit + 1)
+ // Should eat all limit * 3 cpu without any starvation
+ val tasks = (1..blockingLimit).map { async(blocking) { barrier.await() } }
+
+ tasks.forEach { require(it.isActive) }
+ barrier.await()
+ tasks.joinAll()
+ cpuTasks.forEach { it.cancelAndJoin() }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt
new file mode 100644
index 00000000..ce5ed999
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.util.concurrent.*
+
+class BlockingCoroutineDispatcherTest : SchedulerTestBase() {
+
+ @Test(timeout = 1_000)
+ fun testNonBlockingWithBlockingExternal() = runBlocking {
+ val barrier = CyclicBarrier(2)
+
+ val blockingJob = launch(blockingDispatcher.value) {
+ barrier.await()
+ }
+
+ val nonBlockingJob = launch(dispatcher) {
+ barrier.await()
+ }
+
+ nonBlockingJob.join()
+ blockingJob.join()
+ checkPoolThreadsCreated(2)
+ }
+
+ @Test(timeout = 10_000)
+ fun testNonBlockingFromBlocking() = runBlocking {
+ val barrier = CyclicBarrier(2)
+
+ val blocking = launch(blockingDispatcher.value) {
+ // This task will be stolen
+ launch(dispatcher) {
+ barrier.await()
+ }
+
+ barrier.await()
+ }
+
+ blocking.join()
+ checkPoolThreadsCreated(2)
+ }
+
+ @Test(timeout = 1_000)
+ fun testScheduleBlockingThreadCount() = runTest {
+ // After first iteration pool is idle, repeat, no new threads should be created
+ repeat(2) {
+ val blocking = launch(blockingDispatcher.value) {
+ launch(blockingDispatcher.value) {
+ }
+ }
+
+ blocking.join()
+ // Depends on how fast thread will be created
+ checkPoolThreadsCreated(2..3)
+ }
+ }
+
+ @Test(timeout = 1_000)
+ fun testNoCpuStarvation() = runBlocking {
+ val tasksNum = 100
+ val barrier = CyclicBarrier(tasksNum + 1)
+ val tasks = (1..tasksNum).map { launch(blockingDispatcher.value) { barrier.await() } }
+
+ val cpuTask = launch(dispatcher) {
+ // Do nothing, just complete
+ }
+
+ cpuTask.join()
+ tasks.forEach { require(it.isActive) }
+ barrier.await()
+ tasks.joinAll()
+ checkPoolThreadsCreated(101)
+ }
+
+ @Test(timeout = 1_000)
+ fun testNoCpuStarvationWithMultipleBlockingContexts() = runBlocking {
+ val firstBarrier = CyclicBarrier(11)
+ val secondBarrier = CyclicBarrier(11)
+ val blockingDispatcher = blockingDispatcher(10)
+ val blockingDispatcher2 = blockingDispatcher(10)
+
+ val blockingTasks = (1..10).flatMap {
+ listOf(launch(blockingDispatcher) { firstBarrier.await() }, launch(blockingDispatcher2) { secondBarrier.await() })
+ }
+
+ val cpuTasks = (1..100).map {
+ launch(dispatcher) {
+ // Do nothing, just complete
+ }
+ }.toList()
+
+ cpuTasks.joinAll()
+ blockingTasks.forEach { require(it.isActive) }
+ firstBarrier.await()
+ secondBarrier.await()
+ blockingTasks.joinAll()
+ checkPoolThreadsCreated(21..22)
+ }
+
+ @Test(timeout = 1_000)
+ fun testNoExcessThreadsCreated() = runBlocking {
+ corePoolSize = 4
+
+ val tasksNum = 100
+ val barrier = CyclicBarrier(tasksNum + 1)
+ val blockingTasks = (1..tasksNum).map { launch(blockingDispatcher.value) { barrier.await() } }
+
+ val nonBlockingTasks = (1..tasksNum).map {
+ launch(dispatcher) {
+ yield()
+ }
+ }
+
+ nonBlockingTasks.joinAll()
+ barrier.await()
+ blockingTasks.joinAll()
+ // There may be race when multiple CPU threads are trying to lazily created one more
+ checkPoolThreadsCreated(104..120)
+ }
+
+ @Test
+ fun testBlockingFairness() = runBlocking {
+ corePoolSize = 1
+ maxPoolSize = 1
+
+ val blocking = blockingDispatcher(1)
+ val task = async(dispatcher) {
+ expect(1)
+
+ val nonBlocking = async(dispatcher) {
+ expect(3)
+ }
+
+ val firstBlocking = async(blocking) {
+ expect(2)
+ }
+
+ val secondBlocking = async(blocking) {
+ // Already have 1 queued blocking task, so this one wouldn't be scheduled to head
+ expect(4)
+ }
+
+ listOf(firstBlocking, nonBlocking, secondBlocking).joinAll()
+ finish(5)
+ }
+
+ task.await()
+ }
+
+ @Test
+ fun testBoundedBlockingFairness() = runBlocking {
+ corePoolSize = 1
+ maxPoolSize = 1
+
+ val blocking = blockingDispatcher(2)
+ val task = async(dispatcher) {
+ expect(1)
+
+ val nonBlocking = async(dispatcher) {
+ expect(3)
+ }
+
+ val firstBlocking = async(blocking) {
+ expect(4)
+ }
+
+ val secondNonBlocking = async(dispatcher) {
+ expect(5)
+ }
+
+ val secondBlocking = async(blocking) {
+ expect(2) // <- last submitted blocking is executed first
+ }
+
+ val thirdBlocking = async(blocking) {
+ expect(6) // parallelism level is reached before this task
+ }
+
+ listOf(firstBlocking, nonBlocking, secondBlocking, secondNonBlocking, thirdBlocking).joinAll()
+ finish(7)
+ }
+
+ task.await()
+ }
+
+ @Test(timeout = 1_000)
+ fun testYield() = runBlocking {
+ corePoolSize = 1
+ maxPoolSize = 1
+ val ds = blockingDispatcher(1)
+ val outerJob = launch(ds) {
+ expect(1)
+ val innerJob = launch(ds) {
+ // Do nothing
+ expect(3)
+ }
+
+ expect(2)
+ while (innerJob.isActive) {
+ yield()
+ }
+
+ expect(4)
+ innerJob.join()
+ }
+
+ outerJob.join()
+ finish(5)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testNegativeParallelism() {
+ blockingDispatcher(-1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testZeroParallelism() {
+ blockingDispatcher(0)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingIOTerminationStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingIOTerminationStressTest.kt
new file mode 100644
index 00000000..de59a84a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingIOTerminationStressTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.scheduling.*
+import org.junit.*
+import java.util.*
+import java.util.concurrent.*
+
+class BlockingIOTerminationStressTest : TestBase() {
+ private val baseDispatcher = ExperimentalCoroutineDispatcher(
+ 2, 20,
+ TimeUnit.MILLISECONDS.toNanos(10)
+ )
+ private val ioDispatcher = baseDispatcher.blocking()
+ private val TEST_SECONDS = 3L * stressTestMultiplier
+
+ @After
+ fun tearDown() {
+ baseDispatcher.close()
+ }
+
+ @Test
+ fun testTermination() {
+ val rnd = Random()
+ val deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(TEST_SECONDS)
+ while (System.currentTimeMillis() < deadline) {
+ Thread.sleep(rnd.nextInt(30).toLong())
+ repeat(rnd.nextInt(5) + 1) {
+ GlobalScope.launch(ioDispatcher) {
+ Thread.sleep(rnd.nextInt(5).toLong())
+ }
+ }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt
new file mode 100644
index 00000000..da6ef205
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.atomic.*
+import kotlin.test.*
+
+class CoroutineDispatcherTest : SchedulerTestBase() {
+
+ @After
+ fun tearDown() {
+ schedulerTimeSource = NanoTimeSource
+ }
+
+ @Test
+ fun testSingleThread() = runBlocking {
+ expect(1)
+ withContext(dispatcher) {
+ require(Thread.currentThread() is CoroutineScheduler.Worker)
+ expect(2)
+ val job = async {
+ expect(3)
+ delay(10)
+ expect(4)
+ }
+
+ job.await()
+ expect(5)
+ }
+
+ finish(6)
+ checkPoolThreadsCreated(1)
+ }
+
+ @Test
+ fun testFairScheduling() = runBlocking {
+ corePoolSize = 1
+ expect(1)
+
+ val outerJob = launch(dispatcher) {
+ val d1 = launch(dispatcher) { expect(3) }
+ val d2 = launch(dispatcher) { expect(4) }
+ val d3 = launch(dispatcher) { expect(2) }
+ listOf(d1, d2, d3).joinAll()
+ }
+
+ outerJob.join()
+ finish(5)
+ }
+
+ @Test
+ fun testStealing() = runBlocking {
+ corePoolSize = 2
+ val flag = AtomicBoolean(false)
+ val job = async(context = dispatcher) {
+ expect(1)
+ val innerJob = async {
+ expect(2)
+ flag.set(true)
+ }
+
+ while (!flag.get()) {
+ Thread.yield() // Block current thread, submitted inner job will be stolen
+ }
+
+ innerJob.await()
+ expect(3)
+ }
+
+ job.await()
+ finish(4)
+ checkPoolThreadsCreated(2)
+ }
+
+ @Test
+ fun testNoStealing() = runBlocking {
+ corePoolSize = CORES_COUNT
+ schedulerTimeSource = TestTimeSource(0L)
+ withContext(dispatcher) {
+ val thread = Thread.currentThread()
+ val job = async(dispatcher) {
+ assertEquals(thread, Thread.currentThread())
+ val innerJob = async(dispatcher) {
+ assertEquals(thread, Thread.currentThread())
+ }
+ innerJob.await()
+ }
+
+ job.await()
+ assertEquals(thread, Thread.currentThread())
+ }
+
+ checkPoolThreadsCreated(1..2)
+ }
+
+ @Test
+ fun testDelay() = runBlocking {
+ corePoolSize = 2
+ withContext(dispatcher) {
+ expect(1)
+ delay(10)
+ expect(2)
+ }
+
+ finish(3)
+ checkPoolThreadsCreated(2)
+ }
+
+ @Test
+ fun testWithTimeout() = runBlocking {
+ corePoolSize = CORES_COUNT
+ withContext(dispatcher) {
+ expect(1)
+ val result = withTimeoutOrNull(1000) {
+ expect(2)
+ yield() // yield only now
+ "OK"
+ }
+ assertEquals("OK", result)
+
+ val nullResult = withTimeoutOrNull(1000) {
+ expect(3)
+ while (true) {
+ yield()
+ }
+ }
+
+ assertNull(nullResult)
+ finish(4)
+ }
+
+ checkPoolThreadsCreated(1..CORES_COUNT)
+ }
+
+ @Test
+ fun testMaxSize() = runBlocking {
+ corePoolSize = 1
+ maxPoolSize = 4
+ (1..10).map { launch(blockingDispatcher.value) { Thread.sleep(100) } }.joinAll()
+ checkPoolThreadsCreated(4)
+ }
+
+ @Test(timeout = 1_000)
+ fun testYield() = runBlocking {
+ corePoolSize = 1
+ maxPoolSize = 1
+ val outerJob = launch(dispatcher) {
+ expect(1)
+ val innerJob = launch(dispatcher) {
+ // Do nothing
+ expect(3)
+ }
+
+ expect(2)
+ while (innerJob.isActive) {
+ yield()
+ }
+
+ expect(4)
+ innerJob.join()
+ }
+
+ outerJob.join()
+ finish(5)
+ }
+
+ @Test
+ fun testThreadName() = runBlocking {
+ val initialCount = Thread.getAllStackTraces().keys.asSequence()
+ .count { it is CoroutineScheduler.Worker && it.name.contains("SomeTestName") }
+ assertEquals(0, initialCount)
+ val dispatcher = ExperimentalCoroutineDispatcher(1, 1, IDLE_WORKER_KEEP_ALIVE_NS, "SomeTestName")
+ dispatcher.use {
+ launch(dispatcher) {
+ }.join()
+
+ val count = Thread.getAllStackTraces().keys.asSequence()
+ .count { it is CoroutineScheduler.Worker && it.name.contains("SomeTestName") }
+ assertEquals(1, count)
+ }
+
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt
new file mode 100644
index 00000000..f91b0a91
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import org.junit.Test
+import org.junit.runner.*
+import org.junit.runners.*
+import java.util.*
+import java.util.concurrent.*
+import kotlin.test.*
+
+@RunWith(Parameterized::class)
+class CoroutineSchedulerCloseStressTest(private val mode: Mode) : TestBase() {
+ enum class Mode { CPU, BLOCKING, CPU_LIMITED }
+
+ companion object {
+ @Parameterized.Parameters(name = "mode={0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = Mode.values().map { arrayOf<Any>(it) }
+ }
+
+ private val N_REPEAT = 2 * stressTestMultiplier
+ private val MAX_LEVEL = 5
+ private val N_COROS = (1 shl (MAX_LEVEL + 1)) - 1
+ private val N_THREADS = 4
+ private val rnd = Random()
+
+ private lateinit var closeableDispatcher: ExperimentalCoroutineDispatcher
+ private lateinit var dispatcher: ExecutorCoroutineDispatcher
+ private var closeIndex = -1
+
+ private val started = atomic(0)
+ private val finished = atomic(0)
+
+ @Test
+ fun testNormalClose() {
+ try {
+ launchCoroutines()
+ } finally {
+ closeableDispatcher.close()
+ }
+ }
+
+ @Test
+ fun testRacingClose() {
+ repeat(N_REPEAT) {
+ closeIndex = rnd.nextInt(N_COROS)
+ launchCoroutines()
+ }
+ }
+
+ private fun launchCoroutines() = runBlocking {
+ closeableDispatcher = ExperimentalCoroutineDispatcher(N_THREADS)
+ dispatcher = when (mode) {
+ Mode.CPU -> closeableDispatcher
+ Mode.CPU_LIMITED -> closeableDispatcher.limited(N_THREADS) as ExecutorCoroutineDispatcher
+ Mode.BLOCKING -> closeableDispatcher.blocking(N_THREADS) as ExecutorCoroutineDispatcher
+ }
+ started.value = 0
+ finished.value = 0
+ withContext(dispatcher) {
+ launchChild(0, 0)
+ }
+ assertEquals(N_COROS, started.value)
+ assertEquals(N_COROS, finished.value)
+ }
+
+ private fun CoroutineScope.launchChild(index: Int, level: Int): Job = launch(start = CoroutineStart.ATOMIC) {
+ started.incrementAndGet()
+ try {
+ if (index == closeIndex) closeableDispatcher.close()
+ if (level < MAX_LEVEL) {
+ launchChild(2 * index + 1, level + 1)
+ launchChild(2 * index + 2, level + 1)
+ } else {
+ if (rnd.nextBoolean()) {
+ delay(1000)
+ } else {
+ yield()
+ }
+ }
+ } finally {
+ finished.incrementAndGet()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerShrinkTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerShrinkTest.kt
new file mode 100644
index 00000000..50090b53
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerShrinkTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+@Ignore // these tests are too unstable on Windows, should be virtualized
+class CoroutineSchedulerShrinkTest : SchedulerTestBase() {
+
+ private val blockingTasksCount = CORES_COUNT * 3
+ private val blockingTasksBarrier = CyclicBarrier(blockingTasksCount + 1)
+ lateinit var blocking: CoroutineContext
+
+ @Before
+ fun setUp() {
+ corePoolSize = CORES_COUNT
+ // shutdown after 100ms
+ idleWorkerKeepAliveNs = TimeUnit.MILLISECONDS.toNanos(100)
+ blocking = blockingDispatcher(100)
+ }
+
+ @Test(timeout = 10_000)
+ fun testShrinkOnlyBlockingTasks() = runBlocking {
+ // Init dispatcher
+ async(dispatcher) { }.await()
+ // Pool is initialized with core size in the beginning
+ checkPoolThreadsExist(1..2)
+
+ // Run blocking tasks and check increased threads count
+ val blockingTasks = launchBlocking()
+ checkBlockingTasks(blockingTasks)
+
+ delay(2000)
+ // Pool should shrink to core size +- eps
+ checkPoolThreadsExist(CORES_COUNT..CORES_COUNT + 3)
+ }
+
+ @Test(timeout = 10_000)
+ fun testShrinkMixedWithWorkload() = runBlocking {
+ // Block blockingTasksCount cores in blocking dispatcher
+ val blockingTasks = launchBlocking()
+
+ // Block cores count CPU threads
+ val nonBlockingBarrier = CyclicBarrier(CORES_COUNT + 1)
+ val nonBlockingTasks = (1..CORES_COUNT).map {
+ async(dispatcher) {
+ nonBlockingBarrier.await()
+ }
+ }
+
+ // Check CPU tasks succeeded properly even though blocking tasks acquired everything
+ nonBlockingTasks.forEach { require(it.isActive) }
+ nonBlockingBarrier.await()
+ nonBlockingTasks.joinAll()
+
+ // Check blocking tasks succeeded properly
+ checkBlockingTasks(blockingTasks)
+
+ delay(2000)
+ // Pool should shrink to core size
+ checkPoolThreadsExist(CORES_COUNT..CORES_COUNT + 3)
+ }
+
+ private suspend fun checkBlockingTasks(blockingTasks: List<Deferred<*>>) {
+ checkPoolThreadsExist(blockingTasksCount..corePoolSize + blockingTasksCount)
+ blockingTasksBarrier.await()
+ blockingTasks.joinAll()
+ }
+
+ @Test(timeout = 10_000)
+ fun testShrinkWithExternalTasks() = runBlocking {
+ val nonBlockingBarrier = CyclicBarrier(CORES_COUNT + 1)
+ val blockingTasks = launchBlocking()
+
+ val nonBlockingTasks = (1..CORES_COUNT).map {
+ async(dispatcher) {
+ nonBlockingBarrier.await()
+ }
+ }
+
+ // Tasks that burn CPU. Delay is important so tasks will be scheduled from external thread
+ val busySpinTasks = (1..2).map {
+ async(dispatcher) {
+ while (true) {
+ yield()
+ }
+ }
+ }
+
+ nonBlockingTasks.forEach { require(it.isActive) }
+ nonBlockingBarrier.await()
+ nonBlockingTasks.joinAll()
+
+ checkBlockingTasks(blockingTasks)
+
+ delay(2000)
+ // Pool should shrink almost to core size (+/- eps)
+ checkPoolThreadsExist(CORES_COUNT..CORES_COUNT + 3)
+
+ busySpinTasks.forEach {
+ require(it.isActive)
+ it.cancelAndJoin()
+ }
+ }
+
+ private suspend fun launchBlocking(): List<Deferred<*>> {
+ val result = (1..blockingTasksCount).map {
+ GlobalScope.async(blocking) {
+ blockingTasksBarrier.await()
+ }
+ }
+
+ while (blockingTasksBarrier.numberWaiting != blockingTasksCount) {
+ delay(1)
+ }
+
+ return result
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt
new file mode 100644
index 00000000..683a889e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.scheduling.SchedulerTestBase.Companion.checkPoolThreadsCreated
+import org.junit.*
+import org.junit.Ignore
+import org.junit.Test
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class CoroutineSchedulerStressTest : TestBase() {
+ private var dispatcher: ExperimentalCoroutineDispatcher = ExperimentalCoroutineDispatcher()
+ private val observedThreads = ConcurrentHashMap<Thread, Long>()
+ private val tasksNum = 500_000 * stressMemoryMultiplier()
+
+ private fun stressMemoryMultiplier(): Int {
+ return if (isStressTest) {
+ AVAILABLE_PROCESSORS * 4
+ } else {
+ 1
+ }
+ }
+
+ private val processed = AtomicInteger(0)
+ private val finishLatch = CountDownLatch(1)
+
+ @After
+ fun tearDown() {
+ dispatcher.close()
+ }
+
+ @Test
+ @Suppress("DEPRECATION")
+ @Ignore // this test often fails on windows, todo: figure out how to fix it. See issue #904
+ fun testExternalTasksSubmission() {
+ stressTest(CommonPool)
+ }
+
+ @Test
+ fun testInternalTasksSubmission() {
+ stressTest(dispatcher)
+ }
+
+ @Test
+ fun testStealingFromBlocking() {
+ /*
+ * Work-stealing stress test,
+ * one thread submits pack of tasks, waits until they are completed (to avoid work offloading)
+ * and then repeats, thus never executing its own tasks and relying only on work stealing.
+ */
+ var blockingThread: Thread? = null
+ dispatcher.dispatch(EmptyCoroutineContext, Runnable {
+ // Submit million tasks
+ blockingThread = Thread.currentThread()
+ var submittedTasks = 0
+ while (submittedTasks < tasksNum) {
+
+ ++submittedTasks
+ dispatcher.dispatch(EmptyCoroutineContext, Runnable {
+ processTask()
+ })
+
+ while (submittedTasks - processed.get() > 100) {
+ Thread.yield()
+ }
+ }
+
+ // Block current thread
+ finishLatch.await()
+ })
+
+ finishLatch.await()
+
+ require(!observedThreads.containsKey(blockingThread!!))
+ validateResults()
+ }
+
+ private fun stressTest(submissionInitiator: CoroutineDispatcher) {
+ /*
+ * Run 2 million tasks and validate that
+ * 1) All of them are completed successfully
+ * 2) Every thread executed task at least once
+ */
+ submissionInitiator.dispatch(EmptyCoroutineContext, Runnable {
+ for (i in 1..tasksNum) {
+ dispatcher.dispatch(EmptyCoroutineContext, Runnable {
+ processTask()
+ })
+ }
+ })
+
+ finishLatch.await()
+ val observed = observedThreads.size
+ // on slow machines not all threads can be observed
+ assertTrue(observed in (AVAILABLE_PROCESSORS - 1)..(AVAILABLE_PROCESSORS + 1), "Observed $observed threads with $AVAILABLE_PROCESSORS available processors")
+ validateResults()
+ }
+
+ private fun processTask() {
+ val counter = observedThreads[Thread.currentThread()] ?: 0L
+ observedThreads[Thread.currentThread()] = counter + 1
+
+ if (processed.incrementAndGet() == tasksNum) {
+ finishLatch.countDown()
+ }
+ }
+
+ private fun validateResults() {
+ val result = observedThreads.values.sum()
+ assertEquals(tasksNum.toLong(), result)
+ checkPoolThreadsCreated(AVAILABLE_PROCESSORS)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt
new file mode 100644
index 00000000..780ec1b9
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.TestBase
+import org.junit.Test
+import java.lang.Runnable
+import java.util.concurrent.*
+import java.util.concurrent.CountDownLatch
+import kotlin.coroutines.*
+
+class CoroutineSchedulerTest : TestBase() {
+
+ @Test
+ fun testModesExternalSubmission() { // Smoke
+ CoroutineScheduler(1, 1).use {
+ for (mode in TaskMode.values()) {
+ val latch = CountDownLatch(1)
+ it.dispatch(Runnable {
+ latch.countDown()
+ }, TaskContextImpl(mode))
+
+ latch.await()
+ }
+ }
+ }
+
+ @Test
+ fun testModesInternalSubmission() { // Smoke
+ CoroutineScheduler(2, 2).use {
+ val latch = CountDownLatch(TaskMode.values().size)
+ it.dispatch(Runnable {
+ for (mode in TaskMode.values()) {
+ it.dispatch(Runnable {
+ latch.countDown()
+ }, TaskContextImpl(mode))
+ }
+ })
+
+ latch.await()
+ }
+ }
+
+ @Test
+ fun testNonFairSubmission() {
+ CoroutineScheduler(1, 1).use {
+ val startLatch = CountDownLatch(1)
+ val finishLatch = CountDownLatch(2)
+
+ it.dispatch(Runnable {
+ it.dispatch(Runnable {
+ expect(2)
+ finishLatch.countDown()
+ })
+
+ it.dispatch(Runnable {
+ expect(1)
+ finishLatch.countDown()
+ })
+ })
+
+ startLatch.countDown()
+ finishLatch.await()
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testFairSubmission() {
+ CoroutineScheduler(1, 1).use {
+ val startLatch = CountDownLatch(1)
+ val finishLatch = CountDownLatch(2)
+
+ it.dispatch(Runnable {
+ it.dispatch(Runnable {
+ expect(1)
+ finishLatch.countDown()
+ })
+
+ it.dispatch(Runnable {
+ expect(2)
+ finishLatch.countDown()
+ }, fair = true)
+ })
+
+ startLatch.countDown()
+ finishLatch.await()
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testRngUniformDistribution() {
+ CoroutineScheduler(1, 128).use { scheduler ->
+ val worker = scheduler.Worker(1)
+ testUniformDistribution(worker, 2)
+ testUniformDistribution(worker, 4)
+ testUniformDistribution(worker, 8)
+ testUniformDistribution(worker, 12)
+ testUniformDistribution(worker, 16)
+ }
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testNegativeCorePoolSize() {
+ ExperimentalCoroutineDispatcher(-1, 4)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testNegativeMaxPoolSize() {
+ ExperimentalCoroutineDispatcher(1, -4)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testCorePoolSizeGreaterThanMaxPoolSize() {
+ ExperimentalCoroutineDispatcher(4, 1)
+ }
+
+ @Test
+ fun testSelfClose() {
+ val dispatcher = ExperimentalCoroutineDispatcher(1, 1)
+ val latch = CountDownLatch(1)
+ dispatcher.dispatch(EmptyCoroutineContext, Runnable {
+ dispatcher.close(); latch.countDown()
+ })
+ latch.await()
+ }
+
+ private fun testUniformDistribution(worker: CoroutineScheduler.Worker, bound: Int) {
+ val result = IntArray(bound)
+ val iterations = 10_000_000
+ repeat(iterations) {
+ ++result[worker.nextInt(bound)]
+ }
+
+ val bucketSize = iterations / bound
+ for (i in result) {
+ val ratio = i.toDouble() / bucketSize
+ // 10% deviation
+ check(ratio <= 1.1)
+ check(ratio >= 0.9)
+ }
+ }
+
+ private class TaskContextImpl(override val taskMode: TaskMode) : TaskContext {
+ override fun afterTask() {}
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/LimitingCoroutineDispatcherStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/LimitingCoroutineDispatcherStressTest.kt
new file mode 100644
index 00000000..0123ac5d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/LimitingCoroutineDispatcherStressTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class LimitingCoroutineDispatcherStressTest : SchedulerTestBase() {
+
+ init {
+ corePoolSize = 3
+ }
+
+ private val blocking = blockingDispatcher(2)
+ private val cpuView = view(2)
+ private val cpuView2 = view(2)
+ private val concurrentWorkers = atomic(0)
+ private val iterations = 25_000 * stressTestMultiplierSqrt
+
+ @Test
+ fun testCpuLimitNotExtended() = runBlocking<Unit> {
+ val tasks = ArrayList<Deferred<*>>(iterations * 2)
+ repeat(iterations) {
+ tasks += task(cpuView, 3)
+ tasks += task(cpuView2, 3)
+ }
+
+ tasks.awaitAll()
+ }
+
+ @Test
+ fun testCpuLimitWithBlocking() = runBlocking<Unit> {
+ val tasks = ArrayList<Deferred<*>>(iterations * 2)
+ repeat(iterations) {
+ tasks += task(cpuView, 4)
+ tasks += task(blocking, 4)
+ }
+
+ tasks.awaitAll()
+ }
+
+ private fun task(ctx: CoroutineContext, maxLimit: Int): Deferred<Unit> = GlobalScope.async(ctx) {
+ try {
+ val currentlyExecuting = concurrentWorkers.incrementAndGet()
+ assertTrue(currentlyExecuting <= maxLimit, "Executing: $currentlyExecuting, max limit: $maxLimit")
+ } finally {
+ concurrentWorkers.decrementAndGet()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt
new file mode 100644
index 00000000..b4924277
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.util.concurrent.*
+
+class LimitingDispatcherTest : SchedulerTestBase() {
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testTooLargeView() {
+ view(corePoolSize + 1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testNegativeView() {
+ view(-1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testZeroView() {
+ view(0)
+ }
+
+ @Test(timeout = 10_000)
+ fun testBlockingInterleave() = runBlocking {
+ corePoolSize = 3
+ val view = view(2)
+ val blocking = blockingDispatcher(4)
+ val barrier = CyclicBarrier(6)
+ val tasks = ArrayList<Job>(6)
+ repeat(2) {
+ tasks += async(view) {
+ barrier.await()
+ }
+
+ repeat(2) {
+ tasks += async(blocking) {
+ barrier.await()
+ }
+ }
+ }
+
+ tasks.joinAll()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt b/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt
new file mode 100644
index 00000000..e01f027f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UNUSED_VARIABLE")
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import org.junit.*
+import kotlin.coroutines.*
+
+abstract class SchedulerTestBase : TestBase() {
+ companion object {
+ val CORES_COUNT = AVAILABLE_PROCESSORS
+
+ /**
+ * Asserts that [expectedThreadsCount] pool worker threads were created.
+ * Note that 'created' doesn't mean 'exists' because pool supports dynamic shrinking
+ */
+ fun checkPoolThreadsCreated(expectedThreadsCount: Int = CORES_COUNT) {
+ val threadsCount = maxSequenceNumber()!!
+ require(threadsCount == expectedThreadsCount)
+ { "Expected $expectedThreadsCount pool threads, but has $threadsCount" }
+ }
+
+ /**
+ * Asserts that any number of pool worker threads in [range] were created.
+ * Note that 'created' doesn't mean 'exists' because pool supports dynamic shrinking
+ */
+ fun checkPoolThreadsCreated(range: IntRange) {
+ val maxSequenceNumber = maxSequenceNumber()!!
+ require(maxSequenceNumber in range) { "Expected pool threads to be in interval $range, but has $maxSequenceNumber" }
+ }
+
+ /**
+ * 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()
+ require(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()
+ }
+
+ private fun sequenceNumber(threadName: String): Int {
+ val suffix = threadName.substring(threadName.lastIndexOf("-") + 1)
+ val separatorIndex = suffix.indexOf(' ')
+ if (separatorIndex == -1) {
+ return suffix.toInt()
+ }
+
+ return suffix.substring(0, separatorIndex).toInt()
+ }
+
+ suspend fun Iterable<Job>.joinAll() = forEach { it.join() }
+ }
+
+ private val exception = atomic<Throwable?>(null)
+ private val handler = CoroutineExceptionHandler { _, e -> exception.value = e }
+
+ protected var corePoolSize = 1
+ protected var maxPoolSize = 1024
+ protected var idleWorkerKeepAliveNs = IDLE_WORKER_KEEP_ALIVE_NS
+
+ private var _dispatcher: ExperimentalCoroutineDispatcher? = null
+ protected val dispatcher: CoroutineContext
+ get() {
+ if (_dispatcher == null) {
+ _dispatcher = ExperimentalCoroutineDispatcher(
+ corePoolSize,
+ maxPoolSize,
+ idleWorkerKeepAliveNs
+ )
+ }
+
+ return _dispatcher!! + handler
+ }
+
+ protected var blockingDispatcher = lazy {
+ blockingDispatcher(1000)
+ }
+
+ protected fun blockingDispatcher(parallelism: Int): CoroutineContext {
+ val intitialize = dispatcher
+ return _dispatcher!!.blocking(parallelism) + handler
+ }
+
+ protected fun view(parallelism: Int): CoroutineContext {
+ val intitialize = dispatcher
+ return _dispatcher!!.limited(parallelism) + handler
+ }
+
+ @After
+ fun after() {
+ runBlocking {
+ withTimeout(5_000) {
+ _dispatcher?.close()
+ }
+ }
+ exception.value?.let { throw it }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt
new file mode 100644
index 00000000..6a66da9f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class SharingWorkerClassTest : SchedulerTestBase() {
+ private val threadLocal = ThreadLocal<Int?>()
+
+ @Test
+ fun testSharedThread() = runTest {
+ val dispatcher = ExperimentalCoroutineDispatcher(1, schedulerName = "first")
+ val dispatcher2 = ExperimentalCoroutineDispatcher(1, schedulerName = "second")
+
+ try {
+ withContext(dispatcher) {
+ assertNull(threadLocal.get())
+ threadLocal.set(239)
+ withContext(dispatcher2) {
+ assertNull(threadLocal.get())
+ threadLocal.set(42)
+ }
+
+ assertEquals(239, threadLocal.get())
+ }
+ } finally {
+ dispatcher.close()
+ dispatcher2.close()
+ }
+ }
+
+ @Test(timeout = 5000L)
+ fun testProgress() = runTest {
+ // See #990
+ val cores = Runtime.getRuntime().availableProcessors()
+ repeat(cores + 1) {
+ CoroutineScope(Dispatchers.Default).launch {
+ ExperimentalCoroutineDispatcher(1).close()
+ }.join()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt b/kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt
new file mode 100644
index 00000000..a5c83d32
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2018 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() {
+
+ override fun nanoTime() = time
+
+ fun step(delta: Long = WORK_STEALING_TIME_RESOLUTION_NS) {
+ time += delta
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt
new file mode 100644
index 00000000..41adddce
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import java.util.*
+import java.util.concurrent.*
+import kotlin.concurrent.*
+import kotlin.test.*
+
+class WorkQueueStressTest : TestBase() {
+
+ private val threads = mutableListOf<Thread>()
+ private val offerIterations = 100_000 * stressTestMultiplierSqrt // memory pressure, not CPU time
+ private val stealersCount = 6
+ private val stolenTasks = Array(stealersCount) { Queue() }
+ private val globalQueue = Queue() // only producer will use it
+ private val producerQueue = WorkQueue()
+
+ @Volatile
+ private var producerFinished = false
+
+ @Before
+ fun setUp() {
+ schedulerTimeSource = TestTimeSource(Long.MAX_VALUE) // always steal
+ }
+
+ @After
+ fun tearDown() {
+ schedulerTimeSource = NanoTimeSource
+ }
+
+ @Test
+ fun testStealing() {
+ val startLatch = CountDownLatch(1)
+
+ threads += thread(name = "producer") {
+ startLatch.await()
+ for (i in 1..offerIterations) {
+ while (producerQueue.bufferSize > BUFFER_CAPACITY / 2) {
+ Thread.yield()
+ }
+
+ producerQueue.add(task(i.toLong()), globalQueue)
+ }
+
+ producerFinished = true
+ }
+
+ for (i in 0 until stealersCount) {
+ threads += thread(name = "stealer $i") {
+ val myQueue = WorkQueue()
+ startLatch.await()
+ while (!producerFinished || producerQueue.size() != 0) {
+ myQueue.trySteal(producerQueue, stolenTasks[i])
+ }
+
+ // Drain last element which is not counted in buffer
+ myQueue.trySteal(producerQueue, stolenTasks[i])
+ stolenTasks[i].addAll(myQueue.drain().map { task(it) })
+ }
+ }
+
+ startLatch.countDown()
+ threads.forEach { it.join() }
+ validate()
+ }
+
+ @Test
+ fun testSingleProducerSingleStealer() {
+ val startLatch = CountDownLatch(1)
+ val fakeQueue = Queue()
+ threads += thread(name = "producer") {
+ startLatch.await()
+ for (i in 1..offerIterations) {
+ while (producerQueue.bufferSize == BUFFER_CAPACITY - 1) {
+ Thread.yield()
+ }
+
+ // No offloading to global queue here
+ producerQueue.add(task(i.toLong()), fakeQueue)
+ }
+ }
+
+ val stolen = Queue()
+ threads += thread(name = "stealer") {
+ val myQueue = WorkQueue()
+ startLatch.await()
+ while (stolen.size != offerIterations) {
+ if (!myQueue.trySteal(producerQueue, stolen)) {
+ stolen.addAll(myQueue.drain().map { task(it) })
+ }
+ }
+ stolen.addAll(myQueue.drain().map { task(it) })
+ }
+
+ startLatch.countDown()
+ threads.forEach { it.join() }
+ assertEquals((1L..offerIterations).toSet(), stolen.map { it.submissionTime }.toSet())
+ }
+
+ private fun validate() {
+ val result = mutableSetOf<Long>()
+ for (stolenTask in stolenTasks) {
+ assertEquals(stolenTask.size, stolenTask.map { it }.toSet().size)
+ result += stolenTask.map { it.submissionTime }
+ }
+
+ result += globalQueue.map { it.submissionTime }
+ val expected = (1L..offerIterations).toSet()
+ assertEquals(expected, result, "Following elements are missing: ${(expected - result)}")
+ }
+}
+
+internal class Queue : GlobalQueue() {
+ fun addAll(tasks: Collection<Task>) {
+ tasks.forEach { addLast(it) }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt
new file mode 100644
index 00000000..0c55b184
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class WorkQueueTest : TestBase() {
+
+ private val timeSource = TestTimeSource(0)
+
+ @Before
+ fun setUp() {
+ schedulerTimeSource = timeSource
+ }
+
+ @After
+ fun tearDown() {
+ schedulerTimeSource = NanoTimeSource
+ }
+
+ @Test
+ fun testLastScheduledComesFirst() {
+ val queue = WorkQueue()
+ val globalQueue = GlobalQueue()
+ (1L..4L).forEach { queue.add(task(it), globalQueue) }
+ assertEquals(listOf(4L, 1L, 2L, 3L), queue.drain())
+ }
+
+ @Test
+ fun testWorkOffload() {
+ val queue = WorkQueue()
+ val globalQueue = GlobalQueue()
+ (1L..130L).forEach { queue.add(task(it), globalQueue) }
+
+ val expectedLocalResults = (64L..129L).toMutableList()
+ expectedLocalResults.add(0, 130L)
+ assertEquals(expectedLocalResults, queue.drain())
+ assertEquals((1L..63L).toList(), globalQueue.asTimeList())
+ }
+
+ @Test
+ fun testWorkOffloadPrecision() {
+ val queue = WorkQueue()
+ val globalQueue = GlobalQueue()
+ repeat(128) { require(queue.add(task(0), globalQueue)) }
+ require(globalQueue.isEmpty)
+ require(!queue.add(task(0), globalQueue))
+ require(globalQueue.size == 63)
+ }
+
+ @Test
+ fun testTimelyStealing() {
+ val victim = WorkQueue()
+ val globalQueue = GlobalQueue()
+
+ (1L..96L).forEach { victim.add(task(it), globalQueue) }
+
+ timeSource.step()
+ timeSource.step(2)
+
+ val stealer = WorkQueue()
+ require(stealer.trySteal(victim, globalQueue))
+ assertEquals(arrayListOf(2L, 1L), stealer.drain())
+
+ require(!stealer.trySteal(victim, globalQueue))
+ assertEquals(emptyList(), stealer.drain())
+
+ timeSource.step(3)
+ require(stealer.trySteal(victim, globalQueue))
+ assertEquals(arrayListOf(5L, 3L, 4L), stealer.drain())
+ require(globalQueue.isEmpty)
+ assertEquals((6L..96L).toSet(), victim.drain().toSet())
+ }
+
+ @Test
+ fun testStealingBySize() {
+ val victim = WorkQueue()
+ val globalQueue = GlobalQueue()
+
+ (1L..110L).forEach { victim.add(task(it), globalQueue) }
+ val stealer = WorkQueue()
+ require(stealer.trySteal(victim, globalQueue))
+ assertEquals((1L..13L).toSet(), stealer.drain().toSet())
+
+ require(!stealer.trySteal(victim, globalQueue))
+ require(stealer.drain().isEmpty())
+
+
+ timeSource.step()
+ timeSource.step(13)
+ require(!stealer.trySteal(victim, globalQueue))
+ require(stealer.drain().isEmpty())
+
+ timeSource.step(1)
+ require(stealer.trySteal(victim, globalQueue))
+ assertEquals(arrayListOf(14L), stealer.drain())
+
+ }
+
+ @Test
+ fun testStealingFromHead() {
+ val victim = WorkQueue()
+ val globalQueue = GlobalQueue()
+ (1L..2L).forEach { victim.add(task(it), globalQueue) }
+ timeSource.step()
+ timeSource.step(3)
+
+ val stealer = WorkQueue()
+ require(stealer.trySteal(victim, globalQueue))
+ assertEquals(arrayListOf(1L), stealer.drain())
+
+ require(stealer.trySteal(victim, globalQueue))
+ assertEquals(arrayListOf(2L), stealer.drain())
+ }
+}
+
+internal fun GlobalQueue.asTimeList(): List<Long> {
+ val result = mutableListOf<Long>()
+ var next = removeFirstOrNull()
+ while (next != null) {
+ result += next.submissionTime
+ next = removeFirstOrNull()
+ }
+
+ return result
+}
+
+internal fun task(n: Long) = TaskImpl(Runnable {}, n, NonBlockingContext)
+
+internal fun WorkQueue.drain(): List<Long> {
+ var task: Task? = poll()
+ val result = arrayListOf<Long>()
+ while (task != null) {
+ result += task.submissionTime
+ task = poll()
+ }
+
+ return result
+}
diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt
new file mode 100644
index 00000000..380ec5e8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.intrinsics.*
+import kotlin.test.*
+
+class SelectChannelStressTest: TestBase() {
+
+ @Test
+ fun testSelectSendResourceCleanupArrayChannel() = runTest {
+ val channel = Channel<Int>(1)
+ val n = 10_000_000 * stressTestMultiplier
+ expect(1)
+ channel.send(-1) // fill the buffer, so all subsequent sends cannot proceed
+ repeat(n) { i ->
+ select {
+ channel.onSend(i) { expectUnreached() }
+ default { expect(i + 2) }
+ }
+ }
+ finish(n + 2)
+ }
+
+ @Test
+ fun testSelectReceiveResourceCleanupArrayChannel() = runTest {
+ val channel = Channel<Int>(1)
+ val n = 10_000_000 * stressTestMultiplier
+ expect(1)
+ repeat(n) { i ->
+ select {
+ channel.onReceive { expectUnreached() }
+ default { expect(i + 2) }
+ }
+ }
+ finish(n + 2)
+ }
+
+ @Test
+ fun testSelectSendResourceCleanupRendezvousChannel() = runTest {
+ val channel = Channel<Int>(Channel.RENDEZVOUS)
+ val n = 1_000_000 * stressTestMultiplier
+ expect(1)
+ repeat(n) { i ->
+ select {
+ channel.onSend(i) { expectUnreached() }
+ default { expect(i + 2) }
+ }
+ }
+ finish(n + 2)
+ }
+
+ @Test
+ fun testSelectReceiveResourceRendezvousChannel() = runTest {
+ val channel = Channel<Int>(Channel.RENDEZVOUS)
+ val n = 1_000_000 * stressTestMultiplier
+ expect(1)
+ repeat(n) { i ->
+ select {
+ channel.onReceive { expectUnreached() }
+ default { expect(i + 2) }
+ }
+ }
+ finish(n + 2)
+ }
+
+ internal fun <R> SelectBuilder<R>.default(block: suspend () -> R) {
+ this as SelectBuilderImpl // type assertion
+ if (!trySelect(null)) return
+ block.startCoroutineUnintercepted(this)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectMemoryLeakStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectMemoryLeakStressTest.kt
new file mode 100644
index 00000000..7f924dba
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/selects/SelectMemoryLeakStressTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlin.test.*
+
+class SelectMemoryLeakStressTest : TestBase() {
+ private val nRepeat = 1_000_000 * stressTestMultiplier
+
+ @Test
+ fun testLeakRegisterSend() = runTest {
+ expect(1)
+ val leak = Channel<String>()
+ val data = Channel<Int>(1)
+ repeat(nRepeat) { value ->
+ data.send(value)
+ val bigValue = bigValue() // new instance
+ select {
+ leak.onSend("LEAK") {
+ println("Capture big value into this lambda: $bigValue")
+ expectUnreached()
+ }
+ data.onReceive { received ->
+ assertEquals(value, received)
+ expect(value + 2)
+ }
+ }
+ }
+ finish(nRepeat + 2)
+ }
+
+ @Test
+ fun testLeakRegisterReceive() = runTest {
+ expect(1)
+ val leak = Channel<String>()
+ val data = Channel<Int>(1)
+ repeat(nRepeat) { value ->
+ val bigValue = bigValue() // new instance
+ select<Unit> {
+ leak.onReceive {
+ println("Capture big value into this lambda: $bigValue")
+ expectUnreached()
+ }
+ data.onSend(value) {
+ expect(value + 2)
+ }
+ }
+ assertEquals(value, data.receive())
+ }
+ finish(nRepeat + 2)
+ }
+
+ // capture big value for fast OOM in case of a bug
+ private fun bigValue(): ByteArray = ByteArray(4096)
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt
new file mode 100644
index 00000000..5489ea5d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.sync.*
+import kotlin.test.*
+
+class SelectMutexStressTest : TestBase() {
+ @Test
+ fun testSelectCancelledResourceRelease() = runTest {
+ val n = 1_000 * stressTestMultiplier
+ val mutex = Mutex(true) as MutexImpl // locked
+ expect(1)
+ repeat(n) { i ->
+ val job = launch(kotlin.coroutines.coroutineContext) {
+ expect(i + 2)
+ select<Unit> {
+ mutex.onLock {
+ expectUnreached() // never able to lock
+ }
+ }
+ }
+ yield() // to the launched job, so that it suspends
+ job.cancel() // cancel the job and select
+ yield() // so it can cleanup after itself
+ }
+ assertTrue(mutex.isLocked)
+ assertTrue(mutex.isLockedEmptyQueueState)
+ finish(n + 2)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectPhilosophersStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectPhilosophersStressTest.kt
new file mode 100644
index 00000000..eaff30c1
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/selects/SelectPhilosophersStressTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.selects
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.sync.*
+import org.junit.*
+import org.junit.Assert.*
+
+class SelectPhilosophersStressTest : TestBase() {
+ private val TEST_DURATION = 3000L * stressTestMultiplier
+
+ val n = 10 // number of philosophers
+ private val forks = Array(n) { Mutex() }
+
+ private suspend fun eat(id: Int, desc: String) {
+ val left = forks[id]
+ val right = forks[(id + 1) % n]
+ while (true) {
+ val pair = selectUnbiased<Pair<Mutex, Mutex>> {
+ left.onLock(desc) { left to right }
+ right.onLock(desc) { right to left }
+ }
+ if (pair.second.tryLock(desc)) break
+ pair.first.unlock(desc)
+ pair.second.lock(desc)
+ if (pair.first.tryLock(desc)) break
+ pair.second.unlock(desc)
+ }
+ assertTrue(left.isLocked && right.isLocked)
+ // om, nom, nom --> eating!!!
+ right.unlock(desc)
+ left.unlock(desc)
+ }
+
+ @Test
+ fun testPhilosophers() = runBlocking<Unit> {
+ val timeLimit = System.currentTimeMillis() + TEST_DURATION
+ val philosophers = List<Deferred<Int>>(n) { id ->
+ async {
+ val desc = "Philosopher $id"
+ var eatsCount = 0
+ while (System.currentTimeMillis() < timeLimit) {
+ eat(id, desc)
+ eatsCount++
+ yield()
+ }
+ println("Philosopher $id done, eats $eatsCount times")
+ eatsCount
+ }
+ }
+ val debugJob = launch {
+ delay(3 * TEST_DURATION)
+ println("Test is failing. Lock states are:")
+ forks.withIndex().forEach { (id, mutex) -> println("$id: $mutex") }
+ }
+ val eats = withTimeout(5 * TEST_DURATION) { philosophers.map { it.await() } }
+ debugJob.cancel()
+ eats.withIndex().forEach { (id, eats) ->
+ assertTrue("$id shall not starve", eats > 0)
+ }
+ }
+} \ 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
new file mode 100644
index 00000000..f1d3107a
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.sync
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class MutexStressTest : TestBase() {
+ @Test
+ fun testStress() = runTest {
+ val n = 1000 * stressTestMultiplier
+ val k = 100
+ var shared = 0
+ val mutex = Mutex()
+ val jobs = List(n) {
+ launch {
+ repeat(k) {
+ mutex.lock()
+ shared++
+ mutex.unlock()
+ }
+ }
+ }
+ jobs.forEach { it.join() }
+ assertEquals(n * k, shared)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt b/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt
new file mode 100644
index 00000000..e872b527
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt
@@ -0,0 +1,96 @@
+package kotlinx.coroutines.sync
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import org.junit.After
+import kotlin.test.assertEquals
+
+class SemaphoreStressTest : TestBase() {
+
+ @Test
+ fun stressTestAsMutex() = runTest {
+ val n = 10_000 * stressTestMultiplier
+ val k = 100
+ var shared = 0
+ val semaphore = Semaphore(1)
+ val jobs = List(n) {
+ launch {
+ repeat(k) {
+ semaphore.acquire()
+ shared++
+ semaphore.release()
+ }
+ }
+ }
+ jobs.forEach { it.join() }
+ assertEquals(n * k, shared)
+ }
+
+ @Test
+ fun stressTest() = runTest {
+ val n = 10_000 * stressTestMultiplier
+ val k = 100
+ val semaphore = Semaphore(10)
+ val jobs = List(n) {
+ launch {
+ repeat(k) {
+ semaphore.acquire()
+ semaphore.release()
+ }
+ }
+ }
+ jobs.forEach { it.join() }
+ }
+
+ @Test
+ fun stressCancellation() = runTest {
+ val n = 10_000 * stressTestMultiplier
+ val semaphore = Semaphore(1)
+ semaphore.acquire()
+ repeat(n) {
+ val job = launch {
+ semaphore.acquire()
+ }
+ yield()
+ job.cancelAndJoin()
+ }
+ assertEquals(0, semaphore.availablePermits)
+ semaphore.release()
+ assertEquals(1, semaphore.availablePermits)
+ }
+
+ /**
+ * This checks if repeated releases that race with cancellations put
+ * the semaphore into an incorrect state where permits are leaked.
+ */
+ @Test
+ fun stressReleaseCancelRace() = runTest {
+ val n = 10_000 * stressTestMultiplier
+ val semaphore = Semaphore(1, 1)
+ newSingleThreadContext("SemaphoreStressTest").use { pool ->
+ repeat (n) {
+ // Initially, we hold the permit and no one else can `acquire`,
+ // otherwise it's a bug.
+ assertEquals(0, semaphore.availablePermits)
+ var job1_entered_critical_section = false
+ val job1 = launch(start = CoroutineStart.UNDISPATCHED) {
+ semaphore.acquire()
+ job1_entered_critical_section = true
+ semaphore.release()
+ }
+ // check that `job1` didn't finish the call to `acquire()`
+ assertEquals(false, job1_entered_critical_section)
+ val job2 = launch(pool) {
+ semaphore.release()
+ }
+ // Because `job2` executes in a separate thread, this
+ // cancellation races with the call to `release()`.
+ job1.cancelAndJoin()
+ job2.join()
+ assertEquals(1, semaphore.availablePermits)
+ semaphore.acquire()
+ }
+ }
+ }
+
+}
diff --git a/kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt b/kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt
new file mode 100644
index 00000000..25b90914
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Assert.*
+import kotlin.coroutines.*
+
+class TestCoroutineContextTest {
+ private val injectedContext = TestCoroutineContext()
+
+ @After
+ fun tearDown() {
+ injectedContext.cancelAllActions()
+ }
+
+ @Test
+ fun testDelayWithLaunch() = withTestContext(injectedContext) {
+ val delay = 1000L
+
+ var executed = false
+ launch {
+ suspendedDelayedAction(delay) {
+ executed = true
+ }
+ }
+
+ advanceTimeBy(delay / 2)
+ assertFalse(executed)
+
+ advanceTimeBy(delay / 2)
+ assertTrue(executed)
+ }
+
+ @Test
+ fun testTimeJumpWithLaunch() = withTestContext(injectedContext) {
+ val delay = 1000L
+
+ var executed = false
+ launch {
+ suspendedDelayedAction(delay) {
+ executed = true
+ }
+ }
+
+ advanceTimeTo(delay / 2)
+ assertFalse(executed)
+
+ advanceTimeTo(delay)
+ assertTrue(executed)
+ }
+
+ @Test
+ fun testDelayWithAsync() = withTestContext(injectedContext) {
+ val delay = 1000L
+
+ var executed = false
+ async {
+ suspendedDelayedAction(delay) {
+ executed = true
+ }
+ }
+
+ advanceTimeBy(delay / 2)
+ assertFalse(executed)
+
+ advanceTimeBy(delay / 2)
+ assertTrue(executed)
+ }
+
+ @Test
+ fun testDelayWithRunBlocking() = withTestContext(injectedContext) {
+ val delay = 1000L
+
+ var executed = false
+ runBlocking {
+ suspendedDelayedAction(delay) {
+ executed = true
+ }
+ }
+
+ assertTrue(executed)
+ assertEquals(delay, now())
+ }
+
+ private suspend fun suspendedDelayedAction(delay: Long, action: () -> Unit) {
+ delay(delay)
+ action()
+ }
+
+ @Test
+ fun testDelayedFunctionWithRunBlocking() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedValue = 16
+
+ val result = runBlocking {
+ suspendedDelayedFunction(delay) {
+ expectedValue
+ }
+ }
+
+ assertEquals(expectedValue, result)
+ assertEquals(delay, now())
+ }
+
+ @Test
+ fun testDelayedFunctionWithAsync() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedValue = 16
+
+ val deferred = async {
+ suspendedDelayedFunction(delay) {
+ expectedValue
+ }
+ }
+
+ advanceTimeBy(delay / 2)
+ try {
+ deferred.getCompleted()
+ fail("The Job should not have been completed yet.")
+ } catch (e: Exception) {
+ // Success.
+ }
+
+ advanceTimeBy(delay / 2)
+ assertEquals(expectedValue, deferred.getCompleted())
+ }
+
+ private suspend fun <T> TestCoroutineContext.suspendedDelayedFunction(delay: Long, function: () -> T): T {
+ delay(delay / 4)
+ return async {
+ delay((delay / 4) * 3)
+ function()
+ }.await()
+ }
+
+ @Test
+ fun testBlockingFunctionWithRunBlocking() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedValue = 16
+ val result = runBlocking {
+ suspendedBlockingFunction(delay) {
+ expectedValue
+ }
+ }
+ assertEquals(expectedValue, result)
+ assertEquals(delay, now())
+ }
+
+ @Test
+ fun testBlockingFunctionWithAsync() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedValue = 16
+ var now = 0L
+ val deferred = async {
+ suspendedBlockingFunction(delay) {
+ expectedValue
+ }
+ }
+ now += advanceTimeBy((delay / 4) - 1)
+ assertEquals((delay / 4) - 1, now)
+ assertEquals(now, now())
+ try {
+ deferred.getCompleted()
+ fail("The Job should not have been completed yet.")
+ } catch (e: Exception) {
+ // Success.
+ }
+ now += advanceTimeBy(1)
+ assertEquals(delay, now())
+ assertEquals(now, now())
+ assertEquals(expectedValue, deferred.getCompleted())
+ }
+
+ private suspend fun <T> TestCoroutineContext.suspendedBlockingFunction(delay: Long, function: () -> T): T {
+ delay(delay / 4)
+ return runBlocking {
+ delay((delay / 4) * 3)
+ function()
+ }
+ }
+
+ @Test
+ fun testTimingOutFunctionWithAsyncAndNoTimeout() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedValue = 67
+
+ val result = async {
+ suspendedTimingOutFunction(delay, delay + 1) {
+ expectedValue
+ }
+ }
+
+ triggerActions()
+ assertEquals(expectedValue, result.getCompleted())
+ }
+
+ @Test
+ fun testTimingOutFunctionWithAsyncAndTimeout() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedValue = 67
+
+ val result = async {
+ suspendedTimingOutFunction(delay, delay) {
+ expectedValue
+ }
+ }
+
+ triggerActions()
+ assertTrue(result.getCompletionExceptionOrNull() is TimeoutCancellationException)
+ }
+
+ @Test
+ fun testTimingOutFunctionWithRunBlockingAndTimeout() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedValue = 67
+
+ try {
+ runBlocking {
+ suspendedTimingOutFunction(delay, delay) {
+ expectedValue
+ }
+ }
+ fail("Expected TimeoutCancellationException to be thrown.")
+ } catch (e: TimeoutCancellationException) {
+ // Success
+ } catch (e: Throwable) {
+ fail("Expected TimeoutCancellationException to be thrown: $e")
+ }
+ }
+
+ private suspend fun <T> TestCoroutineContext.suspendedTimingOutFunction(delay: Long, timeOut: Long, function: () -> T): T {
+ return runBlocking {
+ withTimeout(timeOut) {
+ delay(delay / 2)
+ val ret = function()
+ delay(delay / 2)
+ ret
+ }
+ }
+ }
+
+ @Test(expected = AssertionError::class)
+ fun testWithTestContextThrowingAnAssertionError() = withTestContext(injectedContext) {
+ val expectedError = IllegalAccessError("hello")
+
+ launch {
+ throw expectedError
+ }
+
+ triggerActions()
+ }
+
+ @Test
+ fun testExceptionHandlingWithLaunch() = withTestContext(injectedContext) {
+ val expectedError = IllegalAccessError("hello")
+
+ launch {
+ throw expectedError
+ }
+
+ triggerActions()
+ assertUnhandledException { it === expectedError}
+ }
+
+ @Test
+ fun testExceptionHandlingWithLaunchingChildCoroutines() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedError = TestException("hello")
+ val expectedValue = 12
+
+ launch {
+ suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, true)
+ }
+
+ advanceTimeBy(delay)
+ assertUnhandledException { it === expectedError}
+ }
+
+ @Test
+ fun testExceptionHandlingWithAsyncAndDontWaitForException() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedError = IllegalAccessError("hello")
+ val expectedValue = 12
+
+ val result = async {
+ suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, false)
+ }
+
+ advanceTimeBy(delay)
+
+ assertNull(result.getCompletionExceptionOrNull())
+ assertEquals(expectedValue, result.getCompleted())
+ }
+
+ @Test
+ fun testExceptionHandlingWithAsyncAndWaitForException() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedError = TestException("hello")
+ val expectedValue = 12
+
+ val result = async {
+ suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, true)
+ }
+
+ advanceTimeBy(delay)
+
+ val e = result.getCompletionExceptionOrNull()
+ assertTrue("Expected to be thrown: '$expectedError' but was '$e'", expectedError === e)
+ }
+
+ @Test
+ fun testExceptionHandlingWithRunBlockingAndDontWaitForException() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedError = IllegalAccessError("hello")
+ val expectedValue = 12
+
+ val result = runBlocking {
+ suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, false)
+ }
+
+ advanceTimeBy(delay)
+
+ assertEquals(expectedValue, result)
+ }
+
+ @Test
+ fun testExceptionHandlingWithRunBlockingAndWaitForException() = withTestContext(injectedContext) {
+ val delay = 1000L
+ val expectedError = TestException("hello")
+ val expectedValue = 12
+
+ try {
+ runBlocking {
+ suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, true)
+ }
+ fail("Expected to be thrown: '$expectedError'")
+ } catch (e: AssertionError) {
+ throw e
+ } catch (e: Throwable) {
+ assertTrue("Expected to be thrown: '$expectedError' but was '$e'", expectedError === e)
+ }
+ }
+
+ private suspend fun <T> TestCoroutineContext.suspendedAsyncWithExceptionAfterDelay(delay: Long, exception: Throwable, value: T, await: Boolean): T {
+ val deferred = async {
+ delay(delay - 1)
+ throw exception
+ }
+
+ if (await) {
+ deferred.await()
+ }
+ return value
+ }
+
+ @Test
+ fun testCancellationException() = withTestContext {
+ val job = launch {
+ delay(1000)
+ }
+
+ advanceTimeBy(500)
+ job.cancel()
+ assertAllUnhandledExceptions { it is CancellationException }
+ }
+
+ @Test
+ fun testCancellationExceptionNotThrownByWithTestContext() = withTestContext {
+ val job = launch {
+ delay(1000)
+ }
+
+ advanceTimeBy(500)
+ job.cancel()
+ }
+}
+
+
+/* Some helper functions */
+// todo: deprecate, replace, see https://github.com/Kotlin/kotlinx.coroutines/issues/541
+private fun TestCoroutineContext.launch(
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ block: suspend CoroutineScope.() -> Unit
+) =
+ GlobalScope.launch(this + (parent ?: EmptyCoroutineContext), start, block)
+
+// todo: deprecate, replace, see https://github.com/Kotlin/kotlinx.coroutines/issues/541
+private fun <T> TestCoroutineContext.async(
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ block: suspend CoroutineScope.() -> T
+
+) =
+ GlobalScope.async(this + (parent ?: EmptyCoroutineContext), start, block)
+
+private fun <T> TestCoroutineContext.runBlocking(
+ block: suspend CoroutineScope.() -> T
+) = runBlocking(this, block)
diff --git a/kotlinx-coroutines-core/native/src/Builders.kt b/kotlinx-coroutines-core/native/src/Builders.kt
new file mode 100644
index 00000000..0dc90d55
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/Builders.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.cinterop.*
+import platform.posix.*
+import kotlin.coroutines.*
+
+/**
+ * Runs new coroutine and **blocks** current thread _interruptibly_ until its completion.
+ * This function should not be used from coroutine. It is designed to bridge regular blocking code
+ * to libraries that are written in suspending style, to be used in `main` functions and in tests.
+ *
+ * The default [CoroutineDispatcher] for this builder in an implementation of [EventLoop] that processes continuations
+ * in this blocked thread until the completion of this coroutine.
+ * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`.
+ *
+ * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of
+ * the specified dispatcher while the current thread is blocked. If the specified dispatcher implements [EventLoop]
+ * interface and this `runBlocking` invocation is performed from inside of the this event loop's thread, then
+ * this event loop is processed using its [processNextEvent][EventLoop.processNextEvent] method until coroutine completes.
+ *
+ * If this blocked thread is interrupted (see [Thread.interrupt]), then the coroutine job is cancelled and
+ * this `runBlocking` invocation throws [InterruptedException].
+ *
+ * See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
+ *
+ * @param context context of the coroutine. The default value is an implementation of [EventLoop].
+ * @param block the coroutine code.
+ */
+public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
+ val contextInterceptor = context[ContinuationInterceptor]
+ val eventLoop: EventLoop?
+ var newContext: CoroutineContext = context // todo: kludge for data flow analysis error
+ if (contextInterceptor == null) {
+ // create or use private event loop if no dispatcher is specified
+ eventLoop = ThreadLocalEventLoop.eventLoop
+ newContext = GlobalScope.newCoroutineContext(context + eventLoop)
+ } else {
+ // See if context's interceptor is an event loop that we shall use (to support TestContext)
+ // or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
+ eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
+ ?: ThreadLocalEventLoop.currentOrNull()
+ newContext = GlobalScope.newCoroutineContext(context)
+ }
+ val coroutine = BlockingCoroutine<T>(newContext, eventLoop)
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+ return coroutine.joinBlocking()
+}
+
+private class BlockingCoroutine<T>(
+ parentContext: CoroutineContext,
+ private val eventLoop: EventLoop?
+) : AbstractCoroutine<T>(parentContext, true) {
+ override val isScopedCoroutine: Boolean get() = true
+
+ @Suppress("UNCHECKED_CAST")
+ fun joinBlocking(): T = memScoped {
+ try {
+ eventLoop?.incrementUseCount()
+ val timespec = alloc<timespec>()
+ while (true) {
+ val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
+ // note: process next even may loose unpark flag, so check if completed before parking
+ if (isCompleted) break
+ timespec.tv_sec = (parkNanos / 1000000000L).convert() // 1e9 ns -> sec
+ timespec.tv_nsec = (parkNanos % 1000000000L).convert() // % 1e9
+ nanosleep(timespec.ptr, null)
+ }
+ } finally { // paranoia
+ eventLoop?.decrementUseCount()
+ }
+ // now return result
+ val state = state
+ (state as? CompletedExceptionally)?.let { throw it.cause }
+ state as T
+ }
+}
diff --git a/kotlinx-coroutines-core/native/src/CompletionHandler.kt b/kotlinx-coroutines-core/native/src/CompletionHandler.kt
new file mode 100644
index 00000000..ac833acb
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/CompletionHandler.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+
+internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler {
+ actual abstract override fun invoke(cause: Throwable?)
+}
+
+internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler get() = this
+
+internal actual abstract class CancelHandlerBase actual constructor() : CompletionHandler {
+ actual abstract override fun invoke(cause: Throwable?)
+}
+
+internal actual inline val CancelHandlerBase.asHandler: CompletionHandler get() = this
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun CompletionHandler.invokeIt(cause: Throwable?) = invoke(cause)
diff --git a/kotlinx-coroutines-core/native/src/CoroutineContext.kt b/kotlinx-coroutines-core/native/src/CoroutineContext.kt
new file mode 100644
index 00000000..41d2c88d
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/CoroutineContext.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+private fun takeEventLoop(): EventLoopImpl =
+ ThreadLocalEventLoop.currentOrNull() as? EventLoopImpl ?:
+ error("There is no event loop. Use runBlocking { ... } to start one.")
+
+internal actual object DefaultExecutor : CoroutineDispatcher(), Delay {
+ override fun dispatch(context: CoroutineContext, block: Runnable) =
+ takeEventLoop().dispatch(context, block)
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) =
+ takeEventLoop().scheduleResumeAfterDelay(timeMillis, continuation)
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle =
+ takeEventLoop().invokeOnTimeout(timeMillis, block)
+
+ actual fun enqueue(task: Runnable): Unit = loopWasShutDown()
+}
+
+internal fun loopWasShutDown(): Nothing = error("Cannot execute task because event loop was shut down")
+
+internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
+ DefaultExecutor
+
+internal actual val DefaultDelay: Delay = DefaultExecutor
+
+public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
+ val combined = coroutineContext + context
+ return if (combined !== DefaultExecutor && combined[ContinuationInterceptor] == null)
+ combined + DefaultExecutor else combined
+}
+
+// No debugging facilities on native
+internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, 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 \ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt
new file mode 100644
index 00000000..5efa9249
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
+ // log exception
+ exception.printStackTrace()
+}
diff --git a/kotlinx-coroutines-core/native/src/Debug.kt b/kotlinx-coroutines-core/native/src/Debug.kt
new file mode 100644
index 00000000..653cb069
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/Debug.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.math.*
+
+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.classSimpleName: String get() = this::class.simpleName ?: "Unknown"
+
+@SymbolName("Kotlin_Any_hashCode")
+external fun Any.id(): Int // Note: can return negative value on K/N
+
+internal actual inline fun assert(value: () -> Boolean) {} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/Dispatchers.kt b/kotlinx-coroutines-core/native/src/Dispatchers.kt
new file mode 100644
index 00000000..8aed2129
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/Dispatchers.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+public actual object Dispatchers {
+
+ public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
+
+ public actual val Main: MainCoroutineDispatcher = NativeMainDispatcher(Default)
+
+ public actual val Unconfined: CoroutineDispatcher get() = kotlinx.coroutines.Unconfined // Avoid freezing
+}
+
+private class NativeMainDispatcher(val delegate: CoroutineDispatcher) : MainCoroutineDispatcher() {
+
+ override val immediate: MainCoroutineDispatcher
+ get() = throw UnsupportedOperationException("Immediate dispatching is not supported on Native")
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) = delegate.dispatch(context, block)
+
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean = delegate.isDispatchNeeded(context)
+
+ override fun dispatchYield(context: CoroutineContext, block: Runnable) = delegate.dispatchYield(context, block)
+
+ override fun toString(): String = delegate.toString()
+}
diff --git a/kotlinx-coroutines-core/native/src/EventLoop.kt b/kotlinx-coroutines-core/native/src/EventLoop.kt
new file mode 100644
index 00000000..8f2dfeb3
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/EventLoop.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.system.*
+
+internal actual abstract class EventLoopImplPlatform: EventLoop() {
+ protected actual fun unpark() { /* does nothing */ }
+ protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask): Unit =
+ loopWasShutDown()
+}
+
+internal class EventLoopImpl: EventLoopImplBase() {
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle =
+ scheduleInvokeOnTimeout(timeMillis, block)
+}
+
+internal actual fun createEventLoop(): EventLoop = EventLoopImpl()
+
+internal actual fun nanoTime(): Long = getTimeNanos() \ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/Exceptions.kt b/kotlinx-coroutines-core/native/src/Exceptions.kt
new file mode 100644
index 00000000..109b9100
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/Exceptions.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * This exception gets thrown if an exception is caught while processing [CompletionHandler] invocation for [Job].
+ *
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual class CompletionHandlerException public actual constructor(
+ message: String,
+ public override val cause: Throwable
+) : RuntimeException(message.withCause(cause))
+
+/**
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending.
+ * It indicates _normal_ cancellation of a coroutine.
+ * **It is not printed to console/log by default uncaught exception handler**.
+ * (see [CoroutineExceptionHandler]).
+ */
+public actual open class CancellationException actual constructor(message: String?) : IllegalStateException(message)
+
+/**
+ * Creates a cancellation exception with a specified message and [cause].
+ */
+@Suppress("FunctionName")
+public actual fun CancellationException(message: String?, cause: Throwable?) : CancellationException =
+ CancellationException(message.withCause(cause))
+
+/**
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed
+ * without cause, or with a cause or exception that is not [CancellationException]
+ * (see [Job.getCancellationException]).
+ */
+internal actual class JobCancellationException public actual constructor(
+ message: String,
+ public override val cause: Throwable?,
+ internal actual val job: Job
+) : CancellationException(message.withCause(cause)) {
+ override fun toString(): String = "${super.toString()}; job=$job"
+ override fun equals(other: Any?): Boolean =
+ other === this ||
+ other is JobCancellationException && other.message == message && other.job == job && other.cause == cause
+ override fun hashCode(): Int =
+ (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0)
+}
+
+@Suppress("FunctionName")
+internal fun IllegalStateException(message: String, cause: Throwable?) =
+ IllegalStateException(message.withCause(cause))
+
+private fun String?.withCause(cause: Throwable?) =
+ when {
+ cause == null -> this
+ this == null -> "caused by $cause"
+ else -> "$this; caused by $cause"
+ }
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) { /* empty */ }
+
+// For use in tests
+internal actual val RECOVER_STACK_TRACES: Boolean = false
diff --git a/kotlinx-coroutines-core/native/src/Runnable.kt b/kotlinx-coroutines-core/native/src/Runnable.kt
new file mode 100644
index 00000000..b240a943
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/Runnable.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * A runnable task for [CoroutineDispatcher.dispatch].
+ */
+public actual interface Runnable {
+ /**
+ * @suppress
+ */
+ public actual fun run()
+}
+
+/**
+ * Creates [Runnable] task instance.
+ */
+@Suppress("FunctionName")
+public actual inline fun Runnable(crossinline block: () -> Unit): Runnable =
+ object : Runnable {
+ override fun run() {
+ block()
+ }
+ }
diff --git a/kotlinx-coroutines-core/native/src/SchedulerTask.kt b/kotlinx-coroutines-core/native/src/SchedulerTask.kt
new file mode 100644
index 00000000..2da03c1b
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/SchedulerTask.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+internal actual abstract class SchedulerTask : Runnable
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias SchedulerTaskContext = Unit
+
+internal actual val SchedulerTask.taskContext: SchedulerTaskContext get() = kotlin.Unit
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun SchedulerTaskContext.afterTask() {}
diff --git a/kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt
new file mode 100644
index 00000000..4a291ea2
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow.internal
+
+import kotlinx.coroutines.*
+
+internal actual class AbortFlowException : CancellationException("Flow was aborted, no more elements needed")
+internal actual class ChildCancelledException : CancellationException("Child of the scoped flow was cancelled")
+
diff --git a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
new file mode 100644
index 00000000..546d6af9
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+internal actual typealias ReentrantLock = NoOpLock
+
+internal actual inline fun <T> ReentrantLock.withLock(action: () -> T) = action()
+
+internal class NoOpLock {
+ fun tryLock() = true
+ fun unlock(): Unit {}
+}
+
+internal actual fun <E> subscriberList(): MutableList<E> = CopyOnWriteList<E>()
+
+internal actual fun <E> identitySet(expectedSize: Int): MutableSet<E> = HashSet()
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias SharedImmutable = kotlin.native.concurrent.SharedImmutable
diff --git a/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
new file mode 100644
index 00000000..c81f6530
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+@Suppress("UNCHECKED_CAST")
+internal class CopyOnWriteList<E>(private var array: Array<Any?> = arrayOfNulls(4)) : AbstractMutableList<E>() {
+
+ private var _size = 0
+ override val size: Int get() = _size
+
+ override fun add(element: E): Boolean {
+ val newSize = if (_size == array.size) array.size * 2 else array.size
+ val update = array.copyOf(newSize)
+ update[_size++] = element
+ array = update
+ return true
+ }
+
+ override fun add(index: Int, element: E) {
+ rangeCheck(index)
+ val update = arrayOfNulls<Any?>(if (array.size == _size) array.size * 2 else array.size)
+ array.copyInto(
+ destination = update,
+ endIndex = index
+ )
+ update[index] = element
+ array.copyInto(
+ destination = update,
+ destinationOffset = index + 1,
+ startIndex = index,
+ endIndex = _size + 1
+ )
+ ++_size
+ array = update
+ }
+
+ override fun remove(element: E): Boolean {
+ val index = array.indexOf(element as Any)
+ if (index == -1) {
+ return false
+ }
+
+ removeAt(index)
+ return true
+ }
+
+ override fun removeAt(index: Int): E {
+ rangeCheck(index)
+ modCount++
+ val n = array.size
+ val element = array[index]
+ val update = arrayOfNulls<Any>(n)
+ array.copyInto(
+ destination = update,
+ endIndex = index
+ )
+ array.copyInto(
+ destination = update,
+ destinationOffset = index,
+ startIndex = index + 1,
+ endIndex = n
+ )
+ array = update
+ --_size
+ return element as E
+ }
+
+ override fun iterator(): MutableIterator<E> = IteratorImpl(array as Array<E>, size)
+
+ override fun listIterator(): MutableListIterator<E> = throw UnsupportedOperationException("Operation is not supported")
+
+ override fun listIterator(index: Int): MutableListIterator<E> = throw UnsupportedOperationException("Operation is not supported")
+
+ override fun isEmpty(): Boolean = size == 0
+
+ override fun set(index: Int, element: E): E = throw UnsupportedOperationException("Operation is not supported")
+
+ override fun get(index: Int): E = array[rangeCheck(index)]!! as E
+
+ private class IteratorImpl<E>(private var array: Array<E>, private val size: Int) : MutableIterator<E> {
+
+ private var current = 0
+
+ override fun hasNext(): Boolean = current != size
+
+ override fun next(): E {
+ if (!hasNext()) {
+ throw NoSuchElementException()
+ }
+
+ return array[current++]!!
+ }
+
+ override fun remove() = throw UnsupportedOperationException("Operation is not supported")
+ }
+
+ private fun rangeCheck(index: Int) = index.apply {
+ if (index < 0 || index >= _size) {
+ throw IndexOutOfBoundsException("index: $index, size: $size")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/native/src/internal/LinkedList.kt b/kotlinx-coroutines-core/native/src/internal/LinkedList.kt
new file mode 100644
index 00000000..07fe1a06
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/LinkedList.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+private typealias Node = LinkedListNode
+
+/** @suppress **This is unstable API and it is subject to change.** */
+@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // :TODO: Remove when fixed: https://youtrack.jetbrains.com/issue/KT-23703
+public actual typealias LockFreeLinkedListNode = LinkedListNode
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual typealias LockFreeLinkedListHead = LinkedListHead
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public open class LinkedListNode {
+ @PublishedApi internal var _next = this
+ @PublishedApi internal var _prev = this
+ @PublishedApi internal var _removed: Boolean = false
+
+ public inline val nextNode get() = _next
+ public inline val prevNode get() = _prev
+ public inline val isRemoved get() = _removed
+
+ public fun addLast(node: Node) {
+ val prev = this._prev
+ node._next = this
+ node._prev = prev
+ prev._next = node
+ this._prev = node
+ }
+
+ public open fun remove(): Boolean {
+ if (_removed) return false
+ val prev = this._prev
+ val next = this._next
+ prev._next = next
+ next._prev = prev
+ _removed = true
+ return true
+ }
+
+ public fun addOneIfEmpty(node: Node): Boolean {
+ if (_next !== this) return false
+ addLast(node)
+ return true
+ }
+
+ public inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean {
+ if (!condition()) return false
+ addLast(node)
+ return true
+ }
+
+ public inline fun addLastIfPrev(node: Node, predicate: (Node) -> Boolean): Boolean {
+ if (!predicate(_prev)) return false
+ addLast(node)
+ return true
+ }
+
+ public inline fun addLastIfPrevAndIf(
+ node: Node,
+ predicate: (Node) -> Boolean, // prev node predicate
+ crossinline condition: () -> Boolean // atomically checked condition
+ ): Boolean {
+ if (!predicate(_prev)) return false
+ if (!condition()) return false
+ addLast(node)
+ return true
+ }
+
+ public fun helpRemove() {} // no-op without multithreading
+
+ public fun removeFirstOrNull(): Node? {
+ val next = _next
+ if (next === this) return null
+ check(next.remove()) { "Should remove" }
+ return next
+ }
+
+ public inline fun <reified T> removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? {
+ val next = _next
+ if (next === this) return null
+ if (next !is T) return null
+ if (predicate(next)) return next
+ check(next.remove()) { "Should remove" }
+ return next
+ }
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual open class AddLastDesc<T : Node> actual constructor(
+ actual val queue: Node,
+ actual val node: T
+) : AbstractAtomicDesc() {
+ protected override val affectedNode: Node get() = queue._prev
+ protected actual override fun onPrepare(affected: Node, next: Node): Any? = null
+ protected override fun onComplete() = queue.addLast(node)
+ protected actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual open class RemoveFirstDesc<T> actual constructor(
+ actual val queue: LockFreeLinkedListNode
+) : AbstractAtomicDesc() {
+
+ @Suppress("UNCHECKED_CAST")
+ public actual val result: T get() = affectedNode as T
+ protected override val affectedNode: Node = queue.nextNode
+ protected actual open fun validatePrepared(node: T): Boolean = true
+ protected actual final override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? {
+ @Suppress("UNCHECKED_CAST")
+ validatePrepared(affectedNode as T)
+ return null
+ }
+ protected override fun onComplete() { queue.removeFirstOrNull() }
+ protected actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public actual abstract class AbstractAtomicDesc : AtomicDesc() {
+ protected abstract val affectedNode: Node
+ protected actual abstract fun onPrepare(affected: Node, next: Node): Any?
+ protected abstract fun onComplete()
+
+ actual final override fun prepare(op: AtomicOp<*>): Any? {
+ val affected = affectedNode
+ val next = affected._next
+ val failure = failure(affected)
+ if (failure != null) return failure
+ return onPrepare(affected, next)
+ }
+
+ actual final override fun complete(op: AtomicOp<*>, failure: Any?) = onComplete()
+ protected actual open fun failure(affected: LockFreeLinkedListNode): Any? = null // Never fails by default
+ protected actual open fun retry(affected: LockFreeLinkedListNode, next: Any): Boolean = false // Always succeeds
+ protected actual abstract fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode)
+}
+
+/** @suppress **This is unstable API and it is subject to change.** */
+public open class LinkedListHead : LinkedListNode() {
+ public val isEmpty get() = _next === this
+
+ /**
+ * Iterates over all elements in this list of a specified type.
+ */
+ public inline fun <reified T : Node> forEach(block: (T) -> Unit) {
+ var cur: Node = _next
+ while (cur != this) {
+ if (cur is T) block(cur)
+ cur = cur._next
+ }
+ }
+
+ // just a defensive programming -- makes sure that list head sentinel is never removed
+ public final override fun remove(): Boolean = throw UnsupportedOperationException()
+}
diff --git a/kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt
new file mode 100644
index 00000000..81b6476b
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> = completion
diff --git a/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt
new file mode 100644
index 00000000..4faf16ac
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+internal actual fun <E: Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E = exception
+internal actual fun <E: Throwable> recoverStackTrace(exception: E): E = exception
+internal actual fun <E : Throwable> unwrap(exception: E): E = exception
+internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing = throw exception
+
+@Suppress("UNUSED")
+internal actual interface CoroutineStackFrame {
+ public actual val callerFrame: CoroutineStackFrame?
+ public actual fun getStackTraceElement(): StackTraceElement?
+}
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias StackTraceElement = Any
diff --git a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
new file mode 100644
index 00000000..fbed5462
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual typealias SynchronizedObject = Any
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
+ block() \ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/internal/SystemProps.kt b/kotlinx-coroutines-core/native/src/internal/SystemProps.kt
new file mode 100644
index 00000000..6779d4e5
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/SystemProps.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+internal actual fun systemProp(propertyName: String): String? = null \ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/native/src/internal/ThreadContext.kt
new file mode 100644
index 00000000..d9daf256
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/ThreadContext.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+internal actual fun threadContextElements(context: CoroutineContext): Any = 0
diff --git a/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt
new file mode 100644
index 00000000..c214a63b
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias NativeThreadLocal = kotlin.native.concurrent.ThreadLocal
+
+internal actual class CommonThreadLocal<T> actual constructor() {
+ private var value: T? = null
+ @Suppress("UNCHECKED_CAST")
+ actual fun get(): T = value as T
+ actual fun set(value: T) { this.value = value }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt b/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt
new file mode 100644
index 00000000..a39a59e1
--- /dev/null
+++ b/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class DelayExceptionTest : TestBase() {
+ private object Dispatcher : CoroutineDispatcher() {
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean = true
+ override fun dispatch(context: CoroutineContext, block: Runnable) { block.run() }
+ }
+
+ private lateinit var exception: Throwable
+
+
+ @Test
+ fun testThrowsTce() {
+ CoroutineScope(Dispatcher + CoroutineExceptionHandler { _, e -> exception = e }).launch {
+ delay(10)
+ }
+
+ assertTrue(exception is IllegalStateException)
+ }
+
+ @Test
+ fun testMaxDelay() = runBlocking {
+ expect(1)
+ val job = launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+ yield()
+ job.cancel()
+ finish(3)
+ }
+}
diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt
new file mode 100644
index 00000000..4f41faa5
--- /dev/null
+++ b/kotlinx-coroutines-core/native/test/TestBase.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+public actual open class TestBase actual constructor() {
+ public actual val isStressTest: Boolean = false
+ public actual val stressTestMultiplier: Int = 1
+
+ private var actionIndex = 0
+ private var finished = false
+ private var error: Throwable? = null
+
+ /**
+ * 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.
+ */
+ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
+ public actual fun error(message: Any, cause: Throwable? = null): Nothing {
+ val exception = IllegalStateException(message.toString(), cause)
+ if (error == null) error = exception
+ throw exception
+ }
+
+ private fun printError(message: String, cause: Throwable) {
+ if (error == null) error = cause
+ println("$message: $cause")
+ }
+
+ /**
+ * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
+ */
+ public actual fun expect(index: Int) {
+ val wasIndex = ++actionIndex
+ check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
+ }
+
+ /**
+ * Asserts that this line is never executed.
+ */
+ public actual fun expectUnreached() {
+ error("Should not be reached")
+ }
+
+ /**
+ * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
+ */
+ public actual fun finish(index: Int) {
+ expect(index)
+ check(!finished) { "Should call 'finish(...)' at most once" }
+ finished = true
+ }
+
+ /**
+ * Asserts that [finish] was invoked
+ */
+ public actual fun ensureFinished() {
+ require(finished) { "finish(...) should be caller prior to this check" }
+ }
+
+ public actual fun reset() {
+ check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
+ actionIndex = 0
+ finished = false
+ }
+
+ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
+ public actual fun runTest(
+ expected: ((Throwable) -> Boolean)? = null,
+ unhandled: List<(Throwable) -> Boolean> = emptyList(),
+ block: suspend CoroutineScope.() -> Unit
+ ) {
+ var exCount = 0
+ var ex: Throwable? = null
+ try {
+ runBlocking(block = block, context = CoroutineExceptionHandler { context, e ->
+ if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
+ exCount++
+ when {
+ exCount > unhandled.size ->
+ printError("Too many unhandled exceptions $exCount, expected ${unhandled.size}, got: $e", e)
+ !unhandled[exCount - 1](e) ->
+ printError("Unhandled exception was unexpected: $e", e)
+ }
+ })
+ } catch (e: Throwable) {
+ ex = e
+ if (expected != null) {
+ if (!expected(e))
+ error("Unexpected exception: $e", e)
+ } else
+ throw e
+ } finally {
+ if (ex == null && expected != null) error("Exception was expected but none produced")
+ }
+ if (exCount < unhandled.size)
+ error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
+ }
+}
diff --git a/kotlinx-coroutines-core/native/test/WorkerTest.kt b/kotlinx-coroutines-core/native/test/WorkerTest.kt
new file mode 100644
index 00000000..84acedac
--- /dev/null
+++ b/kotlinx-coroutines-core/native/test/WorkerTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+import kotlin.native.concurrent.*
+import kotlin.test.*
+
+class WorkerTest : TestBase() {
+
+ @Test
+ fun testLaunchInWorker() {
+ val worker = Worker.start()
+ worker.execute(TransferMode.SAFE, { }) {
+ runBlocking {
+ launch { }.join()
+ delay(1)
+ }
+ }.result
+ }
+
+ @Test
+ fun testLaunchInWorkerTroughGlobalScope() {
+ val worker = Worker.start()
+ worker.execute(TransferMode.SAFE, { }) {
+ runBlocking {
+ CoroutineScope(EmptyCoroutineContext).launch {
+ delay(1)
+ }.join()
+ }
+ }.result
+ }
+}
diff --git a/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt b/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt
new file mode 100644
index 00000000..6c1fddfc
--- /dev/null
+++ b/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class LinkedListTest {
+ data class IntNode(val i: Int) : LinkedListNode()
+
+ @Test
+ fun testSimpleAddLastRemove() {
+ val list = LinkedListHead()
+ assertContents(list)
+ val n1 = IntNode(1).apply { list.addLast(this) }
+ assertContents(list, 1)
+ val n2 = IntNode(2).apply { list.addLast(this) }
+ assertContents(list, 1, 2)
+ val n3 = IntNode(3).apply { list.addLast(this) }
+ assertContents(list, 1, 2, 3)
+ val n4 = IntNode(4).apply { list.addLast(this) }
+ assertContents(list, 1, 2, 3, 4)
+ assertTrue(n1.remove())
+ assertContents(list, 2, 3, 4)
+ assertTrue(n3.remove())
+ assertContents(list, 2, 4)
+ assertTrue(n4.remove())
+ assertContents(list, 2)
+ assertTrue(n2.remove())
+ assertFalse(n2.remove())
+ assertContents(list)
+ }
+
+ private fun assertContents(list: LinkedListHead, vararg expected: Int) {
+ val n = expected.size
+ val actual = IntArray(n)
+ var index = 0
+ list.forEach<IntNode> { actual[index++] = it.i }
+ assertEquals(n, index)
+ for (i in 0 until n) assertEquals(expected[i], actual[i], "item i")
+ assertEquals(expected.isEmpty(), list.isEmpty)
+ }
+}
diff --git a/kotlinx-coroutines-core/npm/README.md b/kotlinx-coroutines-core/npm/README.md
new file mode 100644
index 00000000..7f88ea39
--- /dev/null
+++ b/kotlinx-coroutines-core/npm/README.md
@@ -0,0 +1,19 @@
+# kotlinx.coroutines
+
+Library support for Kotlin coroutines in
+[Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html).
+
+```kotlin
+suspend fun main() = coroutineScope {
+ launch {
+ delay(1000)
+ println("Kotlin Coroutines World!")
+ }
+ println("Hello")
+}
+```
+
+## Documentation
+
+* [Guide to kotlinx.coroutines by example on JVM](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html) (**read it first**)
+* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines)
diff --git a/kotlinx-coroutines-core/npm/package.json b/kotlinx-coroutines-core/npm/package.json
new file mode 100644
index 00000000..5dda3943
--- /dev/null
+++ b/kotlinx-coroutines-core/npm/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "kotlinx-coroutines-core",
+ "version" : "$version",
+ "description" : "Library support for Kotlin coroutines",
+ "main" : "kotlinx-coroutines-core.js",
+ "author": "JetBrains",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/Kotlin/kotlinx.coroutines",
+ "bugs": {
+ "url": "https://github.com/Kotlin/kotlinx.coroutines/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Kotlin/kotlinx.coroutines.git"
+ },
+ "keywords": [
+ "Kotlin",
+ "async",
+ "coroutines",
+ "JavaScript",
+ "JetBrains"
+ ],
+ "peerDependencies": {
+ $kotlinDependency
+ }
+}
diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md
new file mode 100644
index 00000000..ca2a05b6
--- /dev/null
+++ b/kotlinx-coroutines-debug/README.md
@@ -0,0 +1,167 @@
+# Module kotlinx-coroutines-debug
+
+Debugging facilities for `kotlinx.coroutines` on JVM.
+
+### Overview
+
+This module provides a debug JVM agent that allows to track and trace existing coroutines.
+The main entry point to debug facilities is [DebugProbes] API.
+Call to [DebugProbes.install] installs debug agent via ByteBuddy and starts spying on coroutines when they are created, suspended and resumed.
+
+After that, you can use [DebugProbes.dumpCoroutines] to print all active (suspended or running) coroutines, including their state, creation and
+suspension stacktraces.
+Additionally, it is possible to process the list of such coroutines via [DebugProbes.dumpCoroutinesInfo] or dump isolated parts
+of coroutines hierarchy referenced by a [Job] or [CoroutineScope] instances using [DebugProbes.printJob] and [DebugProbes.printScope] respectively.
+
+### Using in your project
+
+Add `kotlinx-coroutines-debug` to your project test dependencies:
+```
+dependencies {
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.1'
+}
+```
+
+### Using in unit tests
+
+For JUnit4 debug module provides special test rule, [CoroutinesTimeout], for installing debug probes
+and to dump coroutines on timeout to simplify tests debugging.
+
+Its usage is better demonstrated by the example (runnable code is [here](test/TestRuleExample.kt)):
+
+```kotlin
+class TestRuleExample {
+ @Rule
+ @JvmField
+ public val timeout = CoroutinesTimeout.seconds(1)
+
+ private suspend fun someFunctionDeepInTheStack() {
+ withContext(Dispatchers.IO) {
+ delay(Long.MAX_VALUE) // Hang method
+ }
+ }
+
+ @Test
+ fun hangingTest() = runBlocking {
+ val job = launch {
+ someFunctionDeepInTheStack()
+ }
+ job.join() // Join will hang
+ }
+}
+```
+
+After 1 second, test will fail with `TestTimeoutException` and all coroutines (`runBlocking` and `launch`) and their
+stacktraces will be dumped to the console.
+
+### Using as JVM agent
+
+It is possible to use this module 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.3.1.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.
+
+
+### Example of usage
+
+Capabilities of this module can be demonstrated by the following example
+(runnable code is [here](test/Example.kt)):
+
+```kotlin
+suspend fun computeValue(): String = coroutineScope {
+ val one = async { computeOne() }
+ val two = async { computeTwo() }
+ combineResults(one, two)
+}
+
+suspend fun combineResults(one: Deferred<String>, two: Deferred<String>): String =
+ one.await() + two.await()
+
+suspend fun computeOne(): String {
+ delay(5000)
+ return "4"
+}
+
+suspend fun computeTwo(): String {
+ delay(5000)
+ return "2"
+}
+
+fun main() = runBlocking {
+ DebugProbes.install()
+ val deferred = async { computeValue() }
+ // Delay for some time
+ delay(1000)
+ // Dump running coroutines
+ DebugProbes.dumpCoroutines()
+ println("\nDumping only deferred")
+ DebugProbes.printJob(deferred)
+}
+```
+
+Printed result will be:
+
+```
+Coroutines dump 2018/11/12 21:44:02
+
+Coroutine "coroutine#2":DeferredCoroutine{Active}@289d1c02, state: SUSPENDED
+ at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:99)
+ at ExampleKt.combineResults(Example.kt:11)
+ at ExampleKt$computeValue$2.invokeSuspend(Example.kt:7)
+ at ExampleKt$main$1$deferred$1.invokeSuspend(Example.kt:25)
+ (Coroutine creation stacktrace)
+ at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)
+ at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)
+ at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
+ at ExampleKt$main$1.invokeSuspend(Example.kt:25)
+ at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
+ at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
+ at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
+ at ExampleKt.main(Example.kt:23)
+ at ExampleKt.main(Example.kt)
+
+... More coroutines here ...
+
+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)
+```
+
+### Status of the API
+
+API is purely experimental and it is not guaranteed that it won't be changed (while it is marked as `@ExperimentalCoroutinesApi`).
+Do not use this module in production environment and do not rely on the format of the data produced by [DebugProbes].
+
+### 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 androin-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)
+-->
+
+<!--- 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
+[DebugProbes.dumpCoroutinesInfo]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines-info.html
+[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
+<!--- 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/build.gradle b/kotlinx-coroutines-debug/build.gradle
new file mode 100644
index 00000000..420a88bd
--- /dev/null
+++ b/kotlinx-coroutines-debug/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+apply plugin: "com.github.johnrengelman.shadow"
+
+configurations {
+ shadowDeps // shaded dependencies, not included into the resulting .pom file
+ compileOnly.extendsFrom(shadowDeps)
+ runtimeOnly.extendsFrom(shadowDeps)
+
+ /*
+ * It is possible to extend a particular configuration with shadow,
+ * but in that case it changes dependency type to "runtime" and resolves it
+ * (so it cannot be further modified). Otherwise, shadow just ignores all dependencies.
+ */
+ shadow.extendsFrom(compile) // shadow - resulting configuration with shaded jar file
+ configureKotlinJvmPlatform(shadow)
+}
+
+dependencies {
+ compileOnly "junit:junit:$junit_version"
+ shadowDeps "net.bytebuddy:byte-buddy:$byte_buddy_version"
+ shadowDeps "net.bytebuddy:byte-buddy-agent:$byte_buddy_version"
+}
+
+jar {
+ manifest {
+ attributes "Premain-Class": "kotlinx.coroutines.debug.AgentPremain"
+ attributes "Can-Redefine-Classes": "true"
+ }
+}
+
+shadowJar {
+ classifier null
+ // Shadow only byte buddy, do not package kotlin stdlib
+ configurations = [project.configurations.shadowDeps]
+ relocate 'net.bytebuddy', 'kotlinx.coroutines.repackaged.net.bytebuddy'
+}
diff --git a/kotlinx-coroutines-debug/src/AgentPremain.kt b/kotlinx-coroutines-debug/src/AgentPremain.kt
new file mode 100644
index 00000000..1ff996e5
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/AgentPremain.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug
+
+import net.bytebuddy.agent.*
+import sun.misc.*
+import java.lang.instrument.*
+
+@Suppress("unused")
+internal object AgentPremain {
+
+ @JvmStatic
+ public fun premain(args: String?, instrumentation: Instrumentation) {
+ Installer.premain(args, instrumentation)
+ DebugProbes.install()
+ installSignalHandler()
+ }
+
+ private fun installSignalHandler() {
+ try {
+ Signal.handle(Signal("TRAP")) { // kill -5
+ DebugProbes.dumpCoroutines()
+ }
+ } catch (t: Throwable) {
+ System.err.println("Failed to install signal handler: $t")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-debug/src/CoroutineInfo.kt b/kotlinx-coroutines-debug/src/CoroutineInfo.kt
new file mode 100644
index 00000000..84cd9f37
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/CoroutineInfo.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PropertyName")
+
+package kotlinx.coroutines.debug
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.coroutines.jvm.internal.*
+
+/**
+ * Class describing coroutine info such as its context, state and stacktrace.
+ */
+@ExperimentalCoroutinesApi
+public class CoroutineInfo internal constructor(
+ val context: CoroutineContext,
+ private val creationStackBottom: CoroutineStackFrame,
+ @JvmField internal val sequenceNumber: Long
+) {
+
+ /**
+ * [Job] associated with a current coroutine or null.
+ * May be later used in [DebugProbes.printJob].
+ */
+ public val job: Job? get() = context[Job]
+
+ /**
+ * Creation stacktrace of the coroutine.
+ */
+ public val creationStackTrace: List<StackTraceElement> get() = creationStackTrace()
+
+ /**
+ * Last observed [state][State] of the coroutine.
+ */
+ public val state: State get() = _state
+
+ private var _state: State = State.CREATED
+
+ @JvmField
+ internal var lastObservedThread: Thread? = null
+
+ @JvmField
+ internal var lastObservedFrame: CoroutineStackFrame? = null
+
+ public fun copy(): CoroutineInfo = CoroutineInfo(context, creationStackBottom, sequenceNumber).also {
+ it._state = _state
+ it.lastObservedFrame = lastObservedFrame
+ it.lastObservedThread = lastObservedThread
+ }
+
+ /**
+ * Last observed stacktrace of the coroutine captured on its suspension or resumption point.
+ * It means that for [running][State.RUNNING] coroutines resulting stacktrace is inaccurate and
+ * reflects stacktrace of the resumption point, not the actual current stacktrace.
+ */
+ public fun lastObservedStackTrace(): List<StackTraceElement> {
+ var frame: CoroutineStackFrame? = lastObservedFrame ?: return emptyList()
+ val result = ArrayList<StackTraceElement>()
+ while (frame != null) {
+ frame.getStackTraceElement()?.let { result.add(it) }
+ frame = frame.callerFrame
+ }
+ return result
+ }
+
+ private fun creationStackTrace(): List<StackTraceElement> {
+ // Skip "Coroutine creation stacktrace" frame
+ return sequence<StackTraceElement> { yieldFrames(creationStackBottom.callerFrame) }.toList()
+ }
+
+ private tailrec suspend fun SequenceScope<StackTraceElement>.yieldFrames(frame: CoroutineStackFrame?) {
+ if (frame == null) return
+ frame.getStackTraceElement()?.let { yield(it) }
+ val caller = frame.callerFrame
+ if (caller != null) {
+ yieldFrames(caller)
+ }
+ }
+
+ internal fun updateState(state: State, frame: Continuation<*>) {
+ // Propagate only duplicating transitions to running for KT-29997
+ if (_state == state && state == State.SUSPENDED && lastObservedFrame != null) return
+ _state = state
+ lastObservedFrame = frame as? CoroutineStackFrame
+ if (state == State.RUNNING) {
+ lastObservedThread = Thread.currentThread()
+ } else {
+ lastObservedThread = null
+ }
+ }
+
+ override fun toString(): String = "CoroutineInfo(state=$state,context=$context)"
+}
+
+/**
+ * Current state of the coroutine.
+ */
+public enum class State {
+ /**
+ * Created, but not yet started.
+ */
+ CREATED,
+ /**
+ * Started and running.
+ */
+ RUNNING,
+ /**
+ * Suspended.
+ */
+ SUSPENDED
+}
diff --git a/kotlinx-coroutines-debug/src/DebugProbes.kt b/kotlinx-coroutines-debug/src/DebugProbes.kt
new file mode 100644
index 00000000..a81fd7a8
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/DebugProbes.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused")
+
+package kotlinx.coroutines.debug
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.internal.*
+import java.io.*
+import java.lang.management.*
+import java.util.*
+import kotlin.coroutines.*
+
+/**
+ * Debug probes support.
+ *
+ * Debug probes is a dynamic attach mechanism which installs multiple hooks into coroutines machinery.
+ * It slows down all coroutine-related code, but in return provides a lot of diagnostic information, including
+ * asynchronous stack-traces and coroutine dumps (similar to [ThreadMXBean.dumpAllThreads] and `jstack` via [DebugProbes.dumpCoroutines].
+ *
+ * Installed hooks:
+ *
+ * * `probeCoroutineResumed` is invoked on every [Continuation.resume].
+ * * `probeCoroutineSuspended` is invoked on every continuation suspension.
+ * * `probeCoroutineCreated` is invoked on every coroutine creation using stdlib intrinsics.
+ *
+ * Overhead:
+ * * Every created coroutine is stored in a weak hash map, thus adding additional GC pressure.
+ * * On every created coroutine, stacktrace of the current thread is dumped.
+ * * On every `resume` and `suspend`, [WeakHashMap] is updated under a global lock.
+ */
+@ExperimentalCoroutinesApi
+public object DebugProbes {
+
+ /**
+ * Whether coroutine creation stacktraces should be sanitized.
+ * Sanitization removes all frames from `kotlinx.coroutines` package except
+ * the first one and the last one to simplify diagnostic.
+ */
+ public var sanitizeStackTraces: Boolean = true
+
+ /**
+ * Installs a [DebugProbes] instead of no-op stdlib probes by redefining
+ * debug probes class using the same class loader as one loaded [DebugProbes] class.
+ */
+ public fun install() {
+ DebugProbesImpl.install()
+ }
+
+ /**
+ * Uninstall debug probes.
+ */
+ public fun uninstall() {
+ DebugProbesImpl.uninstall()
+ }
+
+ /**
+ * Invokes given block of code with installed debug probes and uninstall probes in the end.
+ */
+ public inline fun withDebugProbes(block: () -> Unit) {
+ install()
+ try {
+ block()
+ } finally {
+ uninstall()
+ }
+ }
+
+ /**
+ * Returns string representation of the coroutines [job] hierarchy with additional debug information.
+ * Hierarchy is printed from the [job] as a root transitively to all children.
+ */
+ public fun jobToString(job: Job): String = DebugProbesImpl.hierarchyToString(job)
+
+ /**
+ * Returns string representation of all coroutines launched within the given [scope].
+ * Throws [IllegalStateException] if the scope has no a job in it.
+ */
+ public fun scopeToString(scope: CoroutineScope): String =
+ jobToString(scope.coroutineContext[Job] ?: error("Job is not present in the scope"))
+
+ /**
+ * Prints [job] hierarchy representation from [jobToString] to the given [out].
+ */
+ public fun printJob(job: Job, out: PrintStream = System.out): Unit =
+ out.println(DebugProbesImpl.hierarchyToString(job))
+
+ /**
+ * Prints all coroutines launched within the given [scope].
+ * Throws [IllegalStateException] if the scope has no a job in it.
+ */
+ public fun printScope(scope: CoroutineScope, out: PrintStream = System.out): Unit =
+ printJob(scope.coroutineContext[Job] ?: error("Job is not present in the scope"), out)
+
+ /**
+ * Returns all existing coroutines info.
+ * The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation.
+ */
+ public fun dumpCoroutinesInfo(): List<CoroutineInfo> = DebugProbesImpl.dumpCoroutinesInfo()
+
+ /**
+ * Dumps all active coroutines into the given output stream, providing a consistent snapshot of all existing coroutines at the moment of invocation.
+ * The output of this method is similar to `jstack` or a full thread dump. It can be used as the replacement to
+ * "Dump threads" action.
+ *
+ * Example of the output:
+ * ```
+ * Coroutines dump 2018/11/12 19:45:14
+ *
+ * Coroutine "coroutine#42":StandaloneCoroutine{Active}@58fdd99, state: SUSPENDED
+ * at MyClass$awaitData.invokeSuspend(MyClass.kt:37)
+ * (Coroutine creation stacktrace)
+ * at MyClass.createIoRequest(MyClass.kt:142)
+ * at MyClass.fetchData(MyClass.kt:154)
+ * at MyClass.showData(MyClass.kt:31)
+ *
+ * ...
+ * ```
+ */
+ public fun dumpCoroutines(out: PrintStream = System.out) = 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: kotlin.coroutines.Continuation<T>): kotlin.coroutines.Continuation<T> =
+ DebugProbesImpl.probeCoroutineCreated(completion)
diff --git a/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt b/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt
new file mode 100644
index 00000000..b8b01c35
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.*
+import net.bytebuddy.*
+import net.bytebuddy.agent.*
+import net.bytebuddy.dynamic.loading.*
+import java.io.*
+import java.text.*
+import java.util.*
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+import kotlin.coroutines.*
+import kotlin.coroutines.jvm.internal.*
+import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // IDEA bug workaround
+
+/**
+ * Mirror of [DebugProbes] with actual implementation.
+ * [DebugProbes] are implemented with pimpl to simplify user-facing class and make it look simple and
+ * documented.
+ */
+internal object DebugProbesImpl {
+ private const val ARTIFICIAL_FRAME_MESSAGE = "Coroutine creation stacktrace"
+ private val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
+ private val capturedCoroutines = HashSet<CoroutineOwner<*>>()
+ @Volatile
+ private var installations = 0
+ private val isInstalled: Boolean get() = installations > 0
+ // To sort coroutines by creation order, used as unique id
+ private var sequenceNumber: Long = 0
+
+ /*
+ * This is an optimization in the face of KT-29997:
+ * Consider suspending call stack a()->b()->c() and c() completes its execution and every call is
+ * "almost" in tail position.
+ *
+ * Then at least three RUNNING -> RUNNING transitions will occur consecutively and complexity of each is O(depth).
+ * To avoid that quadratic complexity, we are caching lookup result for such chains in this map and update it incrementally.
+ */
+ private val callerInfoCache = HashMap<CoroutineStackFrame, CoroutineInfo>()
+
+ @Synchronized
+ public fun install() {
+ if (++installations > 1) return
+
+ ByteBuddyAgent.install()
+ val cl = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt")
+ val cl2 = Class.forName("kotlinx.coroutines.debug.DebugProbesKt")
+
+ ByteBuddy()
+ .redefine(cl2)
+ .name(cl.name)
+ .make()
+ .load(cl.classLoader, ClassReloadingStrategy.fromInstalledAgent())
+ }
+
+ @Synchronized
+ public fun uninstall() {
+ check(isInstalled) { "Agent was not installed" }
+ if (--installations != 0) return
+
+ capturedCoroutines.clear()
+ callerInfoCache.clear()
+ val cl = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt")
+ val cl2 = Class.forName("kotlinx.coroutines.debug.internal.NoOpProbesKt")
+
+ ByteBuddy()
+ .redefine(cl2)
+ .name(cl.name)
+ .make()
+ .load(cl.classLoader, ClassReloadingStrategy.fromInstalledAgent())
+ }
+
+ @Synchronized
+ public fun hierarchyToString(job: Job): String {
+ check(isInstalled) { "Debug probes are not installed" }
+ val jobToStack = capturedCoroutines
+ .filter { it.delegate.context[Job] != null }
+ .associateBy({ it.delegate.context[Job]!! }, { it.info })
+ return buildString {
+ job.build(jobToStack, this, "")
+ }
+ }
+
+ private fun Job.build(map: Map<Job, CoroutineInfo>, builder: StringBuilder, indent: String) {
+ val info = map[this]
+ val newIndent: String
+ if (info == null) { // Append coroutine without stacktrace
+ // Do not print scoped coroutines and do not increase indentation level
+ @Suppress("INVISIBLE_REFERENCE")
+ if (this !is kotlinx.coroutines.internal.ScopeCoroutine<*>) {
+ builder.append("$indent$debugString\n")
+ newIndent = indent + "\t"
+ } else {
+ newIndent = indent
+ }
+ } else {
+ // Append coroutine with its last stacktrace element
+ val element = info.lastObservedStackTrace().firstOrNull()
+ val state = info.state
+ builder.append("$indent$debugString, continuation is $state at line $element\n")
+ newIndent = indent + "\t"
+ }
+ // Append children with new indent
+ for (child in children) {
+ child.build(map, builder, newIndent)
+ }
+ }
+
+ @Suppress("DEPRECATION_ERROR") // JobSupport
+ private val Job.debugString: String get() = if (this is JobSupport) toDebugString() else toString()
+
+ @Synchronized
+ public fun dumpCoroutinesInfo(): List<CoroutineInfo> {
+ check(isInstalled) { "Debug probes are not installed" }
+ return capturedCoroutines.asSequence()
+ .map { it.info.copy() } // Copy as CoroutineInfo can be mutated concurrently by DebugProbes
+ .sortedBy { it.sequenceNumber }
+ .toList()
+ }
+
+ public fun dumpCoroutines(out: PrintStream) {
+ // Avoid inference with other out/err invocations by creating a string first
+ dumpCoroutines().let { out.println(it) }
+ }
+
+ @Synchronized
+ private fun dumpCoroutines(): String = buildString {
+ check(isInstalled) { "Debug probes are not installed" }
+ // Synchronization window can be reduce even more, but no need to do it here
+ append("Coroutines dump ${dateFormat.format(System.currentTimeMillis())}")
+ capturedCoroutines
+ .asSequence()
+ .sortedBy { it.info.sequenceNumber }
+ .forEach { owner ->
+ val info = owner.info
+ val observedStackTrace = info.lastObservedStackTrace()
+ val enhancedStackTrace = enhanceStackTraceWithThreadDump(info, observedStackTrace)
+ val state = if (info.state == State.RUNNING && enhancedStackTrace === observedStackTrace)
+ "${info.state} (Last suspension stacktrace, not an actual stacktrace)"
+ else
+ info.state.toString()
+
+ append("\n\nCoroutine ${owner.delegate}, state: $state")
+ if (observedStackTrace.isEmpty()) {
+ append("\n\tat ${createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE)}")
+ printStackTrace(info.creationStackTrace)
+ } else {
+ printStackTrace(enhancedStackTrace)
+ }
+ }
+ }
+
+ /**
+ * Tries to enhance [coroutineTrace] (obtained by call to [CoroutineInfo.lastObservedStackTrace]) with
+ * thread dump of [CoroutineInfo.lastObservedThread].
+ *
+ * Returns [coroutineTrace] if enhancement was unsuccessful or the enhancement result.
+ */
+ private fun enhanceStackTraceWithThreadDump(
+ info: CoroutineInfo,
+ coroutineTrace: List<StackTraceElement>
+ ): List<StackTraceElement> {
+ val thread = info.lastObservedThread
+ if (info.state != State.RUNNING || thread == null) return coroutineTrace
+ // Avoid security manager issues
+ val actualTrace = runCatching { thread.stackTrace }.getOrNull()
+ ?: return coroutineTrace
+
+ /*
+ * Here goes heuristic that tries to merge two stacktraces: real one
+ * (that has at least one but usually not so many suspend function frames)
+ * and coroutine one that has only suspend function frames.
+ *
+ * Heuristic:
+ * 1) Dump lastObservedThread
+ * 2) Find the next frame after BaseContinuationImpl.resumeWith (continuation machinery).
+ * Invariant: this method is called under the lock, so such method **should** be present
+ * in continuation stacktrace.
+ * 3) Find target method in continuation stacktrace (metadata-based)
+ * 4) Prepend dumped stacktrace (trimmed by target frame) to continuation stacktrace
+ *
+ * Heuristic may fail on recursion and overloads, but it will be automatically improved
+ * with KT-29997.
+ */
+ val indexOfResumeWith = actualTrace.indexOfFirst {
+ it.className == "kotlin.coroutines.jvm.internal.BaseContinuationImpl" &&
+ it.methodName == "resumeWith" &&
+ it.fileName == "ContinuationImpl.kt"
+ }
+
+ val (continuationStartFrame, frameSkipped) = findContinuationStartIndex(
+ indexOfResumeWith,
+ actualTrace,
+ coroutineTrace)
+
+ 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) {
+ result += actualTrace[index]
+ }
+
+ for (index in continuationStartFrame + 1 until coroutineTrace.size) {
+ result += coroutineTrace[index]
+ }
+
+ return result
+ }
+
+ /**
+ * Tries to find the lowest meaningful frame above `resumeWith` in the real stacktrace and
+ * its match in a coroutines stacktrace (steps 2-3 in heuristic).
+ *
+ * This method does more than just matching `realTrace.indexOf(resumeWith) - 1`:
+ * 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
+ */
+ 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
+ }
+
+ private fun findIndexOfFrame(
+ frameIndex: Int,
+ actualTrace: Array<StackTraceElement>,
+ coroutineTrace: List<StackTraceElement>
+ ): Int {
+ val continuationFrame = actualTrace.getOrNull(frameIndex)
+ ?: return -1
+
+ return coroutineTrace.indexOfFirst {
+ it.fileName == continuationFrame.fileName &&
+ it.className == continuationFrame.className &&
+ it.methodName == continuationFrame.methodName
+ }
+ }
+
+ private fun StringBuilder.printStackTrace(frames: List<StackTraceElement>) {
+ frames.forEach { frame ->
+ append("\n\tat $frame")
+ }
+ }
+
+ internal fun probeCoroutineResumed(frame: Continuation<*>) = updateState(frame, State.RUNNING)
+
+ internal fun probeCoroutineSuspended(frame: Continuation<*>) = updateState(frame, State.SUSPENDED)
+
+ private fun updateState(frame: Continuation<*>, state: State) {
+ // KT-29997 is here only since 1.3.30
+ if (state == State.RUNNING && KotlinVersion.CURRENT.isAtLeast(1, 3, 30)) {
+ val stackFrame = frame as? CoroutineStackFrame ?: return
+ updateRunningState(stackFrame, state)
+ return
+ }
+
+ // Find ArtificialStackFrame of the coroutine
+ val owner = frame.owner() ?: return
+ updateState(owner, frame, state)
+ }
+
+ @Synchronized // See comment to callerInfoCache
+ private fun updateRunningState(frame: CoroutineStackFrame, state: State) {
+ if (!isInstalled) return
+ // Lookup coroutine info in cache or by traversing stack frame
+ val info: CoroutineInfo
+ val cached = callerInfoCache.remove(frame)
+ if (cached != null) {
+ info = cached
+ } else {
+ info = frame.owner()?.info ?: return
+ // Guard against improper implementations of CoroutineStackFrame and bugs in the compiler
+ callerInfoCache.remove(info.lastObservedFrame?.realCaller())
+ }
+
+ info.updateState(state, frame as Continuation<*>)
+ // Do not cache it for proxy-classes such as ScopeCoroutines
+ val caller = frame.realCaller() ?: return
+ callerInfoCache[caller] = info
+ }
+
+ private tailrec fun CoroutineStackFrame.realCaller(): CoroutineStackFrame? {
+ val caller = callerFrame ?: return null
+ return if (caller.getStackTraceElement() != null) caller else caller.realCaller()
+ }
+
+ @Synchronized
+ private fun updateState(owner: CoroutineOwner<*>, frame: Continuation<*>, state: State) {
+ if (!isInstalled) return
+ owner.info.updateState(state, frame)
+ }
+
+ private fun Continuation<*>.owner(): CoroutineOwner<*>? = (this as? CoroutineStackFrame)?.owner()
+
+ private tailrec fun CoroutineStackFrame.owner(): CoroutineOwner<*>? =
+ if (this is CoroutineOwner<*>) this else callerFrame?.owner()
+
+ internal fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> {
+ if (!isInstalled) return completion
+ /*
+ * If completion already has an owner, it means that we are in scoped coroutine (coroutineScope, withContext etc.),
+ * then piggyback on its already existing owner and do not replace completion
+ */
+ val owner = completion.owner()
+ if (owner != null) return completion
+ /*
+ * Here we replace completion with a sequence of CoroutineStackFrame objects
+ * which represents creation stacktrace, thus making stacktrace recovery mechanism
+ * even more verbose (it will attach coroutine creation stacktrace to all exceptions),
+ * and then using CoroutineOwner completion as unique identifier of coroutineSuspended/resumed calls.
+ */
+ val stacktrace = sanitizeStackTrace(Exception())
+ val frame = stacktrace.foldRight<StackTraceElement, CoroutineStackFrame?>(null) { frame, acc ->
+ object : CoroutineStackFrame {
+ override val callerFrame: CoroutineStackFrame? = acc
+ override fun getStackTraceElement(): StackTraceElement = frame
+ }
+ }!!
+
+ return createOwner(completion, frame)
+ }
+
+ @Synchronized
+ private fun <T> createOwner(completion: Continuation<T>, frame: CoroutineStackFrame): Continuation<T> {
+ if (!isInstalled) return completion
+ val info = CoroutineInfo(completion.context, frame, ++sequenceNumber)
+ val owner = CoroutineOwner(completion, info, frame)
+ capturedCoroutines += owner
+ return owner
+ }
+
+ @Synchronized
+ private fun probeCoroutineCompleted(owner: CoroutineOwner<*>) {
+ capturedCoroutines.remove(owner)
+ /*
+ * This removal is a guard against improperly implemented CoroutineStackFrame
+ * and bugs in the compiler.
+ */
+ val caller = owner.info.lastObservedFrame?.realCaller()
+ callerInfoCache.remove(caller)
+ }
+
+ /**
+ * This class is injected as completion of all continuations in [probeCoroutineCompleted].
+ * It is owning the coroutine info and responsible for managing all its external info related to debug agent.
+ */
+ private class CoroutineOwner<T>(
+ @JvmField val delegate: Continuation<T>,
+ @JvmField val info: CoroutineInfo,
+ frame: CoroutineStackFrame
+ ) : Continuation<T> by delegate, CoroutineStackFrame by frame {
+ override fun resumeWith(result: Result<T>) {
+ probeCoroutineCompleted(this)
+ delegate.resumeWith(result)
+ }
+
+ override fun toString(): String = delegate.toString()
+ }
+
+ private fun <T : Throwable> sanitizeStackTrace(throwable: T): List<StackTraceElement> {
+ val stackTrace = throwable.stackTrace
+ val size = stackTrace.size
+ val probeIndex = stackTrace.indexOfLast { it.className == "kotlin.coroutines.jvm.internal.DebugProbesKt" }
+
+ if (!DebugProbes.sanitizeStackTraces) {
+ return List(size - probeIndex) {
+ if (it == 0) createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE) else stackTrace[it + probeIndex]
+ }
+ }
+
+ /*
+ * 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]
+ * output will be [e, i1, i3, e, i4, e, i5, i7]
+ */
+ 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
+ } else {
+ result += element
+ includeInternalFrame = true
+ }
+
+ }
+
+ result += stackTrace[size - 1]
+ return result
+ }
+
+ private val StackTraceElement.isInternalMethod: Boolean get() = className.startsWith("kotlinx.coroutines")
+}
diff --git a/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt b/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt
new file mode 100644
index 00000000..d32eeb67
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused", "UNUSED_PARAMETER")
+
+package kotlinx.coroutines.debug.internal
+
+import kotlin.coroutines.*
+
+/*
+ * Empty class used to replace installed agent in the end of debug session
+ */
+@JvmName("probeCoroutineResumed")
+internal fun probeCoroutineResumedNoOp(frame: Continuation<*>) = Unit
+@JvmName("probeCoroutineSuspended")
+internal fun probeCoroutineSuspendedNoOp(frame: Continuation<*>) = Unit
+@JvmName("probeCoroutineCreated")
+internal fun <T> probeCoroutineCreatedNoOp(completion: kotlin.coroutines.Continuation<T>): kotlin.coroutines.Continuation<T> = completion
diff --git a/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt
new file mode 100644
index 00000000..ef81cd4b
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-2019 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.rules.*
+import org.junit.runner.*
+import org.junit.runners.model.*
+import java.util.concurrent.*
+
+/**
+ * Coroutines timeout rule for JUnit4 that is applied to all methods in the class.
+ * This rule is very similar to [Timeout] rule: it runs tests in a separate thread,
+ * fails tests after the given timeout and interrupts test thread.
+ *
+ * Additionally, this rule installs [DebugProbes] and dumps all coroutines at the moment of the timeout.
+ * It may cancel coroutines on timeout if [cancelOnTimeout] set to `true`.
+ *
+ * Example of usage:
+ * ```
+ * class HangingTest {
+ *
+ * @Rule
+ * @JvmField
+ * val timeout = CoroutinesTimeout.seconds(5)
+ *
+ * @Test
+ * fun testThatHangs() = runBlocking {
+ * ...
+ * delay(Long.MAX_VALUE) // somewhere deep in the stack
+ * ...
+ * }
+ * }
+ * ```
+ */
+public class CoroutinesTimeout(
+ private val testTimeoutMs: Long,
+ private val cancelOnTimeout: Boolean = false
+) : TestRule {
+
+ init {
+ require(testTimeoutMs > 0) { "Expected positive test timeout, but had $testTimeoutMs" }
+ }
+
+ companion object {
+ /**
+ * Creates [CoroutinesTimeout] rule with the given timeout in seconds.
+ */
+ public fun seconds(seconds: Int, cancelOnTimeout: Boolean = false): CoroutinesTimeout =
+ seconds(seconds.toLong(), cancelOnTimeout)
+
+ /**
+ * Creates [CoroutinesTimeout] rule with the given timeout in seconds.
+ */
+ public fun seconds(seconds: Long, cancelOnTimeout: Boolean = false): CoroutinesTimeout =
+ CoroutinesTimeout(TimeUnit.SECONDS.toMillis(seconds), cancelOnTimeout) // Overflow is properly handled by TimeUnit
+ }
+
+ /**
+ * @suppress suppress from Dokka
+ */
+ override fun apply(base: Statement, description: Description): Statement =
+ CoroutinesTimeoutStatement(base, description, testTimeoutMs, cancelOnTimeout)
+}
diff --git a/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt
new file mode 100644
index 00000000..88864309
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016-2019 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() {
+ DebugProbes.install()
+ testThread.start()
+ // Await until test is started to take only test execution time into account
+ testStartedLatch.await()
+ try {
+ 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/CoroutinesDumpTest.kt b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt
new file mode 100644
index 00000000..ec727014
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class CoroutinesDumpTest : DebugTestBase() {
+ private val monitor = Any()
+
+ @Test
+ fun testSuspendedCoroutine() = synchronized(monitor) {
+ val deferred = GlobalScope.async {
+ sleepingOuterMethod()
+ }
+
+ awaitCoroutineStarted()
+ Thread.sleep(100) // Let delay be invoked
+ verifyDump(
+ "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: SUSPENDED\n" +
+ "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingNestedMethod(CoroutinesDumpTest.kt:95)\n" +
+ "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingOuterMethod(CoroutinesDumpTest.kt:88)\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" +
+ "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n")
+
+ val found = DebugProbes.dumpCoroutinesInfo().single { it.job === deferred }
+ assertSame(deferred, found.job)
+ runBlocking { deferred.cancelAndJoin() }
+ }
+
+ @Test
+ fun testRunningCoroutine() = synchronized(monitor) {
+ val deferred = GlobalScope.async {
+ activeMethod(shouldSuspend = false)
+ assertTrue(true)
+ }
+
+ awaitCoroutineStarted()
+ verifyDump(
+ "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@227d9994, state: RUNNING (Last suspension stacktrace, not an actual stacktrace)\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" +
+ "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n" +
+ "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:148)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt)\n" +
+ "\tat kotlinx.coroutines.BuildersKt.async(Unknown Source)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" +
+ "\tat kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" +
+ "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.testRunningCoroutine(CoroutinesDumpTest.kt:49)")
+ runBlocking { deferred.cancelAndJoin() }
+ }
+
+ @Test
+ fun testRunningCoroutineWithSuspensionPoint() = synchronized(monitor) {
+ val deferred = GlobalScope.async {
+ activeMethod(shouldSuspend = true)
+ yield() // tail-call
+ }
+
+ awaitCoroutineStarted()
+ Thread.sleep(10)
+ verifyDump(
+ "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: RUNNING\n" +
+ "\tat java.lang.Thread.sleep(Native Method)\n" +
+ "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt:111)\n" +
+ "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt:106)\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" +
+ "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n" +
+ "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:148)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt)\n" +
+ "\tat kotlinx.coroutines.BuildersKt.async(Unknown Source)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" +
+ "\tat kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" +
+ "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.testRunningCoroutineWithSuspensionPoint(CoroutinesDumpTest.kt:71)"
+ )
+ runBlocking { deferred.cancelAndJoin() }
+ }
+
+ @Test
+ fun testCreationStackTrace() = synchronized(monitor) {
+ val deferred = GlobalScope.async {
+ activeMethod(shouldSuspend = true)
+ }
+
+ awaitCoroutineStarted()
+ val coroutine = DebugProbes.dumpCoroutinesInfo().first()
+ val result = coroutine.creationStackTrace.fold(StringBuilder()) { acc, element ->
+ acc.append(element.toString())
+ acc.append('\n')
+ }.toString().trimStackTrace()
+
+ runBlocking { deferred.cancelAndJoin() }
+
+ val expected = ("kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
+ "kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" +
+ "kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)\n" +
+ "kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:160)\n" +
+ "kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:88)\n" +
+ "kotlinx.coroutines.BuildersKt.async(Unknown Source)\n" +
+ "kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt:81)\n" +
+ "kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" +
+ "kotlinx.coroutines.debug.CoroutinesDumpTest.testCreationStackTrace(CoroutinesDumpTest.kt:109)").trimStackTrace()
+ assertTrue(result.startsWith(expected))
+ }
+
+ @Test
+ fun testFinishedCoroutineRemoved() = synchronized(monitor) {
+ val deferred = GlobalScope.async {
+ activeMethod(shouldSuspend = true)
+ }
+
+ awaitCoroutineStarted()
+ runBlocking { deferred.cancelAndJoin() }
+ verifyDump()
+ }
+
+ private suspend fun activeMethod(shouldSuspend: Boolean) {
+ nestedActiveMethod(shouldSuspend)
+ assertTrue(true) // tail-call
+ }
+
+ private suspend fun nestedActiveMethod(shouldSuspend: Boolean) {
+ if (shouldSuspend) yield()
+ notifyTest()
+ while (coroutineContext[Job]!!.isActive) {
+ Thread.sleep(100)
+ }
+ }
+
+ private suspend fun sleepingOuterMethod() {
+ sleepingNestedMethod()
+ yield()
+ }
+
+ private suspend fun sleepingNestedMethod() {
+ yield()
+ notifyTest()
+ delay(Long.MAX_VALUE)
+ }
+
+ private fun awaitCoroutineStarted() {
+ (monitor as Object).wait()
+ }
+
+ private fun notifyTest() {
+ synchronized(monitor) {
+ (monitor as Object).notify()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/DebugProbesTest.kt b/kotlinx-coroutines-debug/test/DebugProbesTest.kt
new file mode 100644
index 00000000..35d02417
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/DebugProbesTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines.debug
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class DebugProbesTest : TestBase() {
+
+ private fun CoroutineScope.createDeferred(): Deferred<*> = async(NonCancellable) {
+ throw ExecutionException(null)
+ }
+
+ @Test
+ fun testAsync() = runTest {
+ val deferred = createDeferred()
+ val traces = listOf(
+ "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",
+ "Caused by: java.util.concurrent.ExecutionException\n" +
+ "\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt:14)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)"
+ )
+ nestedMethod(deferred, traces)
+ deferred.join()
+ }
+
+ @Test
+ fun testAsyncWithProbes() = DebugProbes.withDebugProbes {
+ DebugProbes.sanitizeStackTraces = false
+ runTest {
+ val deferred = createDeferred()
+ val traces = listOf(
+ "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" +
+ "\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.CoroutineStart.invoke(CoroutineStart.kt:99)\n" +
+ "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:148)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:45)\n" +
+ "\tat kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)\n" +
+ "\tat kotlinx.coroutines.TestBase.runTest(TestBase.kt:138)\n" +
+ "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:19)\n" +
+ "\tat kotlinx.coroutines.debug.DebugProbesTest.testAsyncWithProbes(DebugProbesTest.kt:38)",
+ "Caused by: java.util.concurrent.ExecutionException\n" +
+ "\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt:16)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n")
+ nestedMethod(deferred, traces)
+ deferred.join()
+ }
+ }
+
+ @Test
+ fun testAsyncWithSanitizedProbes() = DebugProbes.withDebugProbes {
+ DebugProbes.sanitizeStackTraces = true
+ runTest {
+ val deferred = createDeferred()
+ val traces = listOf(
+ "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" +
+ "\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.debug.DebugProbesTest.testAsyncWithSanitizedProbes(DebugProbesTest.kt:38)",
+ "Caused by: java.util.concurrent.ExecutionException\n" +
+ "\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt:16)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n")
+ nestedMethod(deferred, traces)
+ deferred.join()
+ }
+ }
+
+ private suspend fun nestedMethod(deferred: Deferred<*>, traces: List<String>) {
+ oneMoreNestedMethod(deferred, traces)
+ assertTrue(true) // Prevent tail-call optimization
+ }
+
+ private suspend fun oneMoreNestedMethod(deferred: Deferred<*>, traces: List<String>) {
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: ExecutionException) {
+ verifyStackTrace(e, traces)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/DebugTestBase.kt b/kotlinx-coroutines-debug/test/DebugTestBase.kt
new file mode 100644
index 00000000..3e4abea2
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/DebugTestBase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines.debug
+
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.junit4.*
+import org.junit.*
+
+open class DebugTestBase : TestBase() {
+
+ @JvmField
+ @Rule
+ val timeout = CoroutinesTimeout.seconds(60)
+
+ @Before
+ open fun setUp() {
+ before()
+ DebugProbes.sanitizeStackTraces = false
+ DebugProbes.install()
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ DebugProbes.uninstall()
+ } finally {
+ onCompletion()
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-debug/test/Example.kt b/kotlinx-coroutines-debug/test/Example.kt
new file mode 100644
index 00000000..8a0944cb
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/Example.kt
@@ -0,0 +1,32 @@
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.*
+
+suspend fun computeValue(): String = coroutineScope {
+ val one = async { computeOne() }
+ val two = async { computeTwo() }
+ combineResults(one, two)
+}
+
+suspend fun combineResults(one: Deferred<String>, two: Deferred<String>): String =
+ one.await() + two.await()
+
+suspend fun computeOne(): String {
+ delay(5000)
+ return "4"
+}
+
+suspend fun computeTwo(): String {
+ delay(5000)
+ return "2"
+}
+
+fun main() = runBlocking {
+ DebugProbes.install()
+ val deferred = async { computeValue() }
+ // Delay for some time
+ delay(1000)
+ // Dump running coroutines
+ DebugProbes.dumpCoroutines()
+ println("\nDumping only deferred")
+ DebugProbes.printJob(deferred)
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-debug/test/RecoveryExample.kt b/kotlinx-coroutines-debug/test/RecoveryExample.kt
new file mode 100644
index 00000000..2280dd16
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/RecoveryExample.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("PackageDirectoryMismatch")
+package example
+
+import kotlinx.coroutines.*
+
+object PublicApiImplementation : CoroutineScope by CoroutineScope(CoroutineName("Example")) {
+
+ private fun doWork(): Int {
+ error("Internal invariant failed")
+ }
+
+ private fun asynchronousWork(): Int {
+ return doWork() + 1
+ }
+
+ public suspend fun awaitAsynchronousWorkInMainThread() {
+ val task = async(Dispatchers.Default) {
+ asynchronousWork()
+ }
+
+ task.await()
+ }
+}
+
+suspend fun main() {
+ // Try to switch debug mode on and off to see the difference
+ PublicApiImplementation.awaitAsynchronousWorkInMainThread()
+}
diff --git a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
new file mode 100644
index 00000000..c15fe894
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines.debug
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class RunningThreadStackMergeTest : DebugTestBase() {
+
+ private val testMainBlocker = CountDownLatch(1) // Test body blocks on it
+ private val coroutineBlocker = CyclicBarrier(2) // Launched coroutine blocks on it
+
+ @Test
+ fun testStackMergeWithContext() = runTest {
+ launchCoroutine()
+ awaitCoroutineStarted()
+ verifyDump(
+ "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored
+ "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@50284dc4, state: RUNNING\n" +
+ "\tat sun.misc.Unsafe.park(Native Method)\n" +
+ "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
+ "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
+ "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +
+ "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:86)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.access\$nonSuspendingFun(RunningThreadStackMergeTest.kt:12)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$suspendingFunction\$2.invokeSuspend(RunningThreadStackMergeTest.kt:77)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunction(RunningThreadStackMergeTest.kt:75)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:68)\n" +
+ "\t(Coroutine creation stacktrace)\n" +
+ "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)",
+ ignoredCoroutine = ":BlockingCoroutine"
+ )
+ coroutineBlocker.await()
+ }
+
+ private fun awaitCoroutineStarted() {
+ testMainBlocker.await()
+ while (coroutineBlocker.numberWaiting != 1) {
+ Thread.sleep(10)
+ }
+ }
+
+ private fun CoroutineScope.launchCoroutine() {
+ launch(Dispatchers.Default) {
+ suspendingFunction()
+ assertTrue(true)
+ }
+ }
+
+ private suspend fun suspendingFunction() {
+ // Typical use-case
+ withContext(Dispatchers.IO) {
+ yield()
+ nonSuspendingFun()
+ }
+
+ assertTrue(true)
+ }
+
+ private fun nonSuspendingFun() {
+ testMainBlocker.countDown()
+ coroutineBlocker.await()
+ }
+
+ @Test
+ fun testStackMergeEscapeSuspendMethod() = runTest {
+ launchEscapingCoroutine()
+ awaitCoroutineStarted()
+ Thread.sleep(10)
+ verifyDump(
+ "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored
+ "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" +
+ "\tat sun.misc.Unsafe.park(Native Method)\n" +
+ "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
+ "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
+ "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +
+ "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:83)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.access\$nonSuspendingFun(RunningThreadStackMergeTest.kt:12)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$suspendingFunctionWithContext\$2.invokeSuspend(RunningThreadStackMergeTest.kt:124)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunctionWithContext(RunningThreadStackMergeTest.kt:122)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:116)\n" +
+ "\t(Coroutine creation stacktrace)\n" +
+ "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)",
+ ignoredCoroutine = ":BlockingCoroutine"
+ )
+ coroutineBlocker.await()
+ }
+
+ private fun CoroutineScope.launchEscapingCoroutine() {
+ launch(Dispatchers.Default) {
+ suspendingFunctionWithContext()
+ assertTrue(true)
+ }
+ }
+
+ private suspend fun suspendingFunctionWithContext() {
+ withContext(Dispatchers.IO) {
+ actualSuspensionPoint()
+ nonSuspendingFun()
+ }
+
+ assertTrue(true)
+ }
+
+ @Test
+ fun testMergeThroughInvokeSuspend() = runTest {
+ launchEscapingCoroutineWithoutContext()
+ awaitCoroutineStarted()
+ verifyDump(
+ "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored
+ "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" +
+ "\tat sun.misc.Unsafe.park(Native Method)\n" +
+ "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
+ "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
+ "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +
+ "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:83)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunctionWithoutContext(RunningThreadStackMergeTest.kt:160)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutineWithoutContext\$1.invokeSuspend(RunningThreadStackMergeTest.kt:153)\n" +
+ "\t(Coroutine creation stacktrace)\n" +
+ "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)",
+ ignoredCoroutine = ":BlockingCoroutine"
+ )
+ coroutineBlocker.await()
+ }
+
+ private fun CoroutineScope.launchEscapingCoroutineWithoutContext() {
+ launch(Dispatchers.Default) {
+ suspendingFunctionWithoutContext()
+ assertTrue(true)
+ }
+ }
+
+ private suspend fun suspendingFunctionWithoutContext() {
+ actualSuspensionPoint()
+ nonSuspendingFun()
+ assertTrue(true)
+ }
+
+ @Test
+ fun testRunBlocking() = runBlocking {
+ verifyDump("Coroutine \"coroutine#1\":BlockingCoroutine{Active}@4bcd176c, state: RUNNING\n" +
+ "\tat java.lang.Thread.getStackTrace(Thread.java:1552)\n" +
+ "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.enhanceStackTraceWithThreadDump(DebugProbesImpl.kt:147)\n" +
+ "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.dumpCoroutines(DebugProbesImpl.kt:122)\n" +
+ "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.dumpCoroutines(DebugProbesImpl.kt:109)\n" +
+ "\tat kotlinx.coroutines.debug.DebugProbes.dumpCoroutines(DebugProbes.kt:122)\n" +
+ "\tat kotlinx.coroutines.debug.StracktraceUtilsKt.verifyDump(StracktraceUtils.kt)\n" +
+ "\tat kotlinx.coroutines.debug.StracktraceUtilsKt.verifyDump\$default(StracktraceUtils.kt)\n" +
+ "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$testRunBlocking\$1.invokeSuspend(RunningThreadStackMergeTest.kt:112)\n" +
+ "\t(Coroutine creation stacktrace)\n" +
+ "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n")
+ }
+
+
+ private suspend fun actualSuspensionPoint() {
+ nestedSuspensionPoint()
+ assertTrue(true)
+ }
+
+ private suspend fun nestedSuspensionPoint() {
+ yield()
+ assertTrue(true)
+ }
+
+ @Test
+ fun testActiveThread() = runBlocking<Unit> {
+ launchCoroutine()
+ awaitCoroutineStarted()
+ val info = DebugProbes.dumpCoroutinesInfo().find { it.state == State.RUNNING }
+ assertNotNull(info)
+ @Suppress("INVISIBLE_MEMBER") // IDEA bug
+ assertNotNull(info.lastObservedThread)
+ coroutineBlocker.await()
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt
new file mode 100644
index 00000000..223a3346
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+package definitely.not.kotlinx.coroutines
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.*
+import kotlinx.coroutines.selects.*
+import org.junit.*
+import org.junit.Ignore
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class SanitizedProbesTest : DebugTestBase() {
+ @Before
+ override fun setUp() {
+ super.setUp()
+ DebugProbes.sanitizeStackTraces = true
+ }
+
+ @Test
+ fun testRecoveredStackTrace() = runTest {
+ val deferred = createDeferred()
+ val traces = listOf(
+ "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" +
+ "\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.TestBase.runTest\$default(TestBase.kt:141)\n" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.testRecoveredStackTrace(SanitizedProbesTest.kt:33)",
+ "Caused by: java.util.concurrent.ExecutionException\n" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$createDeferredNested\$1.invokeSuspend(SanitizedProbesTest.kt:57)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n"
+ )
+ nestedMethod(deferred, traces)
+ deferred.join()
+ }
+
+ @Test
+ fun testCoroutinesDump() = runTest {
+ val deferred = createActiveDeferred()
+ yield()
+ verifyDump(
+ "Coroutine \"coroutine#3\":BlockingCoroutine{Active}@7d68ef40, state: RUNNING\n" +
+ "\tat java.lang.Thread.getStackTrace(Thread.java)\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" +
+ "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:141)\n" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.testCoroutinesDump(SanitizedProbesTest.kt:56)",
+
+ "Coroutine \"coroutine#4\":DeferredCoroutine{Active}@75c072cb, state: SUSPENDED\n" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$createActiveDeferred\$1.invokeSuspend(SanitizedProbesTest.kt:63)\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" +
+ "\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" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$testCoroutinesDump\$1.invokeSuspend(SanitizedProbesTest.kt:57)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n" +
+ "\tat kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:237)\n" +
+ "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:141)\n" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.testCoroutinesDump(SanitizedProbesTest.kt:56)"
+ )
+ deferred.cancelAndJoin()
+ }
+
+ @Test
+ fun testSelectBuilder() = runTest {
+ val selector = launchSelector()
+ expect(1)
+ yield()
+ expect(3)
+ verifyDump("Coroutine \"coroutine#1\":BlockingCoroutine{Active}@35fc6dc4, state: RUNNING\n" +
+ "\tat java.lang.Thread.getStackTrace(Thread.java:1552)\n" + // Skip the rest
+ "\t(Coroutine creation stacktrace)\n" +
+ "\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" +
+ "\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.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" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$testSelectBuilder\$1.invokeSuspend(SanitizedProbesTest.kt:89)\n" +
+ "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n" +
+ "\tat kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)\n" +
+ "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:154)\n" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.testSelectBuilder(SanitizedProbesTest.kt:88)")
+ finish(4)
+ selector.cancelAndJoin()
+ }
+
+ private fun CoroutineScope.launchSelector(): Job {
+ val job = CompletableDeferred(Unit)
+ return launch {
+ select<Int> {
+ job.onJoin {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ 1
+ }
+ }
+ }
+ }
+
+ private fun CoroutineScope.createActiveDeferred(): Deferred<*> = async {
+ suspendingMethod()
+ assertTrue(true)
+ }
+
+ private suspend fun suspendingMethod() {
+ delay(Long.MAX_VALUE)
+ }
+
+ private fun CoroutineScope.createDeferred(): Deferred<*> = createDeferredNested()
+
+ private fun CoroutineScope.createDeferredNested(): Deferred<*> = async(NonCancellable) {
+ throw ExecutionException(null)
+ }
+
+ private suspend fun nestedMethod(deferred: Deferred<*>, traces: List<String>) {
+ oneMoreNestedMethod(deferred, traces)
+ assertTrue(true) // Prevent tail-call optimization
+ }
+
+ private suspend fun oneMoreNestedMethod(deferred: Deferred<*>, traces: List<String>) {
+ try {
+ deferred.await()
+ expectUnreached()
+ } catch (e: ExecutionException) {
+ verifyStackTrace(e, traces)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt b/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt
new file mode 100644
index 00000000..c7627255
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug
+
+import kotlinx.coroutines.*
+import org.junit.*
+import kotlin.coroutines.*
+
+class ScopedBuildersTest : DebugTestBase() {
+
+ @Test
+ fun testNestedScopes() = runBlocking {
+ val job = launch { doInScope() }
+ yield()
+ yield()
+ verifyDump(
+ "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@16612a51, state: RUNNING\n" +
+ "\t(Coroutine creation stacktrace)\n" +
+ "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n",
+
+ "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@6b53e23f, state: SUSPENDED\n" +
+ "\tat kotlinx.coroutines.debug.ScopedBuildersTest\$doWithContext\$2.invokeSuspend(ScopedBuildersTest.kt:49)\n" +
+ "\tat kotlinx.coroutines.debug.ScopedBuildersTest.doWithContext(ScopedBuildersTest.kt:47)\n" +
+ "\tat kotlinx.coroutines.debug.ScopedBuildersTest\$doInScope\$2.invokeSuspend(ScopedBuildersTest.kt:41)\n" +
+ "\tat kotlinx.coroutines.debug.ScopedBuildersTest\$testNestedScopes\$1\$job\$1.invokeSuspend(ScopedBuildersTest.kt:30)\n" +
+ "\t(Coroutine creation stacktrace)\n" +
+ "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)")
+ job.cancelAndJoin()
+ finish(4)
+ }
+
+ private suspend fun doInScope() = coroutineScope {
+ expect(1)
+ doWithContext()
+ expectUnreached()
+ }
+
+ private suspend fun doWithContext() {
+ expect(2)
+ withContext(wrapperDispatcher(coroutineContext)) {
+ expect(3)
+ delay(Long.MAX_VALUE)
+ }
+ expectUnreached()
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/StartModeProbesTest.kt b/kotlinx-coroutines-debug/test/StartModeProbesTest.kt
new file mode 100644
index 00000000..a0297da3
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/StartModeProbesTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class StartModeProbesTest : DebugTestBase() {
+
+ @Test
+ fun testUndispatched() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ undispatchedSleeping()
+ assertTrue(true)
+ }
+
+ yield()
+ expect(3)
+ verifyPartialDump(2, "StartModeProbesTest.undispatchedSleeping")
+ job.cancelAndJoin()
+ verifyPartialDump(1, "StartModeProbesTest\$testUndispatched")
+ finish(4)
+ }
+
+ private suspend fun undispatchedSleeping() {
+ delay(Long.MAX_VALUE)
+ assertTrue(true)
+ }
+
+ @Test
+ fun testWithTimeoutWithUndispatched() = runTest {
+ expect(1)
+ val job = launchUndispatched()
+
+ yield()
+ expect(3)
+ verifyPartialDump(
+ 2,
+ "StartModeProbesTest\$launchUndispatched\$1.invokeSuspend",
+ "StartModeProbesTest.withTimeoutHelper",
+ "StartModeProbesTest\$withTimeoutHelper\$2.invokeSuspend"
+ )
+ job.cancelAndJoin()
+ verifyPartialDump(1, "StartModeProbesTest\$testWithTimeoutWithUndispatched")
+ finish(4)
+ }
+
+ private fun CoroutineScope.launchUndispatched(): Job {
+ return launch(start = CoroutineStart.UNDISPATCHED) {
+ withTimeoutHelper()
+ assertTrue(true)
+ }
+ }
+
+ private suspend fun withTimeoutHelper() {
+ withTimeout(Long.MAX_VALUE) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ assertTrue(true)
+ }
+
+ @Test
+ fun testWithTimeout() = runTest {
+ withTimeout(Long.MAX_VALUE) {
+ testActiveDump(
+ false,
+ "StartModeProbesTest\$testWithTimeout\$1.invokeSuspend",
+ "state: RUNNING"
+ )
+ }
+ }
+
+ @Test
+ fun testWithTimeoutAfterYield() = runTest {
+ withTimeout(Long.MAX_VALUE) {
+ testActiveDump(
+ true,
+ "StartModeProbesTest\$testWithTimeoutAfterYield\$1.invokeSuspend",
+ "StartModeProbesTest\$testWithTimeoutAfterYield\$1\$1.invokeSuspend",
+ "StartModeProbesTest.testActiveDump",
+ "state: RUNNING"
+ )
+ }
+ }
+
+ private suspend fun testActiveDump(shouldYield: Boolean, vararg expectedFrames: String) {
+ if (shouldYield) yield()
+ verifyPartialDump(1, *expectedFrames)
+ assertTrue(true)
+ }
+
+ @Test
+ fun testWithTailCall() = runTest {
+ expect(1)
+ val job = tailCallMethod()
+ yield()
+ expect(3)
+ verifyPartialDump(2, "StartModeProbesTest\$launchFromTailCall\$2")
+ job.cancelAndJoin()
+ verifyPartialDump(1, "StartModeProbesTest\$testWithTailCall")
+ finish(4)
+ }
+
+ private suspend fun CoroutineScope.tailCallMethod(): Job = launchFromTailCall()
+ private suspend fun CoroutineScope.launchFromTailCall(): Job = launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ @Test
+ fun testCoroutineScope() = runTest {
+ expect(1)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ runScope()
+ }
+
+ yield()
+ expect(3)
+ verifyPartialDump(
+ 2,
+ "StartModeProbesTest\$runScope\$2.invokeSuspend",
+ "StartModeProbesTest\$testCoroutineScope\$1\$job\$1.invokeSuspend")
+ job.cancelAndJoin()
+ finish(4)
+ }
+
+ private suspend fun runScope() {
+ coroutineScope {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/StracktraceUtils.kt b/kotlinx-coroutines-debug/test/StracktraceUtils.kt
new file mode 100644
index 00000000..cab4ed86
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/StracktraceUtils.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug
+
+import java.io.*
+import kotlin.test.*
+
+public fun String.trimStackTrace(): String =
+ trimIndent()
+ .replace(Regex(":[0-9]+"), "")
+ .replace(Regex("#[0-9]+"), "")
+ .applyBackspace()
+
+public fun String.applyBackspace(): String {
+ val array = toCharArray()
+ val stack = CharArray(array.size)
+ var stackSize = -1
+ for (c in array) {
+ if (c != '\b') {
+ stack[++stackSize] = c
+ } else {
+ --stackSize
+ }
+ }
+
+ return String(stack, 0, stackSize + 1)
+}
+
+public fun verifyStackTrace(e: Throwable, traces: List<String>) {
+ val stacktrace = toStackTrace(e)
+ traces.forEach {
+ val expectedLines = it.trimStackTrace().split("\n")
+ for (i in 0 until expectedLines.size) {
+ traces.forEach {
+ assertTrue(
+ stacktrace.trimStackTrace().contains(it.trimStackTrace()),
+ "\nExpected trace element:\n$it\n\nActual stacktrace:\n$stacktrace"
+ )
+ }
+ }
+ }
+
+ val causes = stacktrace.count("Caused by")
+ assertNotEquals(0, causes)
+ assertEquals(causes, traces.map { it.count("Caused by") }.sum())
+}
+
+public fun toStackTrace(t: Throwable): String {
+ val sw = StringWriter()
+ t.printStackTrace(PrintWriter(sw))
+ return sw.toString()
+}
+
+public fun String.count(substring: String): Int = split(substring).size - 1
+
+public fun verifyDump(vararg traces: String, ignoredCoroutine: String? = null) {
+ val baos = ByteArrayOutputStream()
+ DebugProbes.dumpCoroutines(PrintStream(baos))
+ val trace = baos.toString().split("\n\n")
+ if (traces.isEmpty()) {
+ assertEquals(1, trace.size)
+ assertTrue(trace[0].startsWith("Coroutines dump"))
+ return
+ }
+ // Drop "Coroutine dump" line
+ trace.withIndex().drop(1).forEach { (index, value) ->
+ if (ignoredCoroutine != null && value.contains(ignoredCoroutine)) {
+ return@forEach
+ }
+
+ val expected = traces[index - 1].applyBackspace().split("\n\t(Coroutine creation stacktrace)\n", limit = 2)
+ val actual = value.applyBackspace().split("\n\t(Coroutine creation stacktrace)\n", limit = 2)
+ assertEquals(expected.size, actual.size)
+
+ expected.withIndex().forEach { (index, trace) ->
+ val actualTrace = actual[index].trimStackTrace().sanitizeAddresses()
+ val expectedTrace = trace.trimStackTrace().sanitizeAddresses()
+ val actualLines = actualTrace.split("\n")
+ val expectedLines = expectedTrace.split("\n")
+ for (i in 0 until expectedLines.size) {
+ assertEquals(expectedLines[i], actualLines[i])
+ }
+ }
+ }
+}
+
+public fun String.trimPackage() = replace("kotlinx.coroutines.debug.", "")
+
+public fun verifyPartialDump(createdCoroutinesCount: Int, vararg frames: String) {
+ val baos = ByteArrayOutputStream()
+ DebugProbes.dumpCoroutines(PrintStream(baos))
+ val dump = baos.toString()
+ val trace = dump.split("\n\n")
+ val matches = frames.all { frame ->
+ trace.any { tr -> tr.contains(frame) }
+ }
+
+ assertEquals(createdCoroutinesCount, DebugProbes.dumpCoroutinesInfo().size)
+ assertTrue(matches)
+}
+
+private fun String.sanitizeAddresses(): String {
+ val index = indexOf("coroutine\"")
+ val next = indexOf(',', index)
+ if (index == -1 || next == -1) return this
+ return substring(0, index) + substring(next, length)
+}
diff --git a/kotlinx-coroutines-debug/test/TestRuleExample.kt b/kotlinx-coroutines-debug/test/TestRuleExample.kt
new file mode 100644
index 00000000..b5d1c262
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/TestRuleExample.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.junit4.*
+import org.junit.*
+
+@Ignore // do not run it on CI
+class TestRuleExample {
+
+ @JvmField
+ @Rule
+ public val timeout = CoroutinesTimeout.seconds(1)
+
+ private suspend fun someFunctionDeepInTheStack() {
+ withContext(Dispatchers.IO) {
+ delay(Long.MAX_VALUE)
+ println("This line is never executed")
+ }
+
+ println("This line is never executed as well")
+ }
+
+ @Test
+ fun hangingTest() = runBlocking {
+ val job = launch {
+ someFunctionDeepInTheStack()
+ }
+
+ println("Doing some work...")
+ job.join()
+ }
+
+ @Test
+ fun successfulTest() = runBlocking {
+ launch {
+ delay(10)
+ }.join()
+ }
+
+}
diff --git a/kotlinx-coroutines-debug/test/ToStringTest.kt b/kotlinx-coroutines-debug/test/ToStringTest.kt
new file mode 100644
index 00000000..0a9e84ef
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/ToStringTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.junit.*
+import org.junit.Test
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class ToStringTest : TestBase() {
+
+ @Before
+ fun setUp() {
+ before()
+ DebugProbes.sanitizeStackTraces = false
+ DebugProbes.install()
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ DebugProbes.uninstall()
+ } finally {
+ onCompletion()
+ }
+ }
+
+
+ private suspend fun CoroutineScope.launchNestedScopes(): Job {
+ return launch {
+ expect(1)
+ coroutineScope {
+ expect(2)
+ launchDelayed()
+
+ supervisorScope {
+ expect(3)
+ launchDelayed()
+ }
+ }
+ }
+ }
+
+ private fun CoroutineScope.launchDelayed(): Job {
+ return launch {
+ delay(Long.MAX_VALUE)
+ }
+ }
+
+ @Test
+ fun testPrintHierarchyWithScopes() = runBlocking {
+ val tab = '\t'
+ val expectedString = """
+ "coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchNestedScopes$2$1.invokeSuspend(ToStringTest.kt)
+ $tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt)
+ $tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt)
+ """.trimIndent()
+
+ val job = launchNestedScopes()
+ try {
+ repeat(5) { yield() }
+ val expected = expectedString.trimStackTrace().trimPackage()
+ expect(4)
+ assertEquals(expected, DebugProbes.jobToString(job).trimEnd().trimStackTrace().trimPackage())
+ assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(job)).trimEnd().trimStackTrace().trimPackage())
+ } finally {
+ finish(5)
+ job.cancelAndJoin()
+ }
+ }
+
+ @Test
+ fun testCompletingHierarchy() = runBlocking {
+ val tab = '\t'
+ val expectedString = """
+ "coroutine#2":StandaloneCoroutine{Completing}
+ $tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30)
+ $tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40)
+ $tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37)
+ """.trimIndent()
+
+ checkHierarchy(isCompleting = true, expectedString = expectedString)
+ }
+
+ @Test
+ fun testActiveHierarchy() = runBlocking {
+ val tab = '\t'
+ val expectedString = """
+ "coroutine#2":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1.invokeSuspend(ToStringTest.kt:94)
+ $tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30)
+ $tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40)
+ $tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37)
+ """.trimIndent()
+ checkHierarchy(isCompleting = false, expectedString = expectedString)
+ }
+
+ private suspend fun CoroutineScope.checkHierarchy(isCompleting: Boolean, expectedString: String) {
+ val root = launchHierarchy(isCompleting)
+ repeat(4) { yield() }
+ val expected = expectedString.trimStackTrace().trimPackage()
+ expect(6)
+ assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage())
+ assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).trimEnd().trimStackTrace().trimPackage())
+
+ root.cancelAndJoin()
+ finish(7)
+ }
+
+ private fun CoroutineScope.launchHierarchy(isCompleting: Boolean): Job {
+ return launch {
+ expect(1)
+ async(CoroutineName("foo")) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ actor<Int> {
+ expect(3)
+ val job = launch {
+ expect(4)
+ delay(Long.MAX_VALUE)
+ }
+
+ withContext(wrapperDispatcher(coroutineContext)) {
+ expect(5)
+ job.join()
+ }
+ }
+
+ if (!isCompleting) {
+ delay(Long.MAX_VALUE)
+ }
+ }
+ }
+
+ private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
+ val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
+ return object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ dispatcher.dispatch(context, block)
+ }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt
new file mode 100644
index 00000000..fb170c07
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2019 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.*
+import org.junit.*
+import org.junit.runners.model.*
+
+class CoroutinesTimeoutTest : TestBase() {
+
+ @Rule
+ @JvmField
+ public val validation = TestFailureValidation(
+ 1000, false,
+ TestResultSpec("throwingTest", error = RuntimeException::class.java),
+ TestResultSpec("successfulTest"),
+ TestResultSpec(
+ "hangingTest", expectedOutParts = listOf(
+ "Coroutines dump",
+ "Test hangingTest timed out after 1 seconds",
+ "BlockingCoroutine{Active}",
+ "runBlocking",
+ "at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutTest.suspendForever",
+ "at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutTest\$hangingTest\$1.invokeSuspend"),
+ notExpectedOutParts = listOf("delay", "throwingTest"),
+ error = TestTimedOutException::class.java)
+ )
+
+ @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()
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt b/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt
new file mode 100644
index 00000000..a10c5118
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016-2019 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.rules.*
+import org.junit.runner.*
+import org.junit.runners.model.*
+import java.io.*
+import kotlin.test.*
+
+internal fun TestFailureValidation(timeoutMs: Long, cancelOnTimeout: Boolean, vararg specs: TestResultSpec): RuleChain =
+ RuleChain
+ .outerRule(TestFailureValidation(specs.associateBy { it.testName }))
+ .around(
+ CoroutinesTimeout(
+ timeoutMs,
+ cancelOnTimeout
+ )
+ )
+
+/**
+ * Rule that captures test result, serr and sout and validates it against provided [testsSpec]
+ */
+internal class TestFailureValidation(private val testsSpec: Map<String, TestResultSpec>) : TestRule {
+
+ companion object {
+ init {
+ DebugProbes.sanitizeStackTraces = false
+ }
+ }
+ override fun apply(base: Statement, description: Description): Statement {
+ return TestFailureStatement(base, description)
+ }
+
+ inner class TestFailureStatement(private val test: Statement, private val description: Description) : Statement() {
+ private lateinit var sout: PrintStream
+ private lateinit var serr: PrintStream
+ private val capturedOut = ByteArrayOutputStream()
+
+ override fun evaluate() {
+ try {
+ replaceOut()
+ test.evaluate()
+ } catch (e: Throwable) {
+ validateFailure(e)
+ return
+ } finally {
+ resetOut()
+ }
+
+ validateSuccess() // To avoid falling into catch
+ }
+
+ private fun validateSuccess() {
+ val spec = testsSpec[description.methodName] ?: error("Test spec not found: ${description.methodName}")
+ require(spec.error == null) { "Expected exception of type ${spec.error}, but test successfully passed" }
+
+ val captured = capturedOut.toString()
+ assertFalse(captured.contains("Coroutines dump"))
+ assertTrue(captured.isEmpty(), captured)
+ }
+
+ private fun validateFailure(e: Throwable) {
+ val spec = testsSpec[description.methodName] ?: error("Test spec not found: ${description.methodName}")
+ if (spec.error == null || !spec.error.isInstance(e)) {
+ throw IllegalStateException("Unexpected failure, expected ${spec.error}, had ${e::class}", e)
+ }
+
+ if (e !is TestTimedOutException) return
+
+ val captured = capturedOut.toString()
+ assertTrue(captured.contains("Coroutines dump"))
+ for (part in spec.expectedOutParts) {
+ assertTrue(captured.contains(part), "Expected $part to be part of the\n$captured")
+ }
+
+ for (part in spec.notExpectedOutParts) {
+ assertFalse(captured.contains(part), "Expected $part not to be part of the\n$captured")
+ }
+ }
+
+ private fun replaceOut() {
+ sout = System.out
+ serr = System.err
+
+ System.setOut(PrintStream(capturedOut))
+ System.setErr(PrintStream(capturedOut))
+ }
+
+ private fun resetOut() {
+ System.setOut(sout)
+ System.setErr(serr)
+ }
+ }
+}
+
+data class TestResultSpec(
+ val testName: String, val expectedOutParts: List<String> = listOf(), val notExpectedOutParts:
+ List<String> = listOf(), val error: Class<out Throwable>? = null
+)
diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md
new file mode 100644
index 00000000..014c53b7
--- /dev/null
+++ b/kotlinx-coroutines-test/README.md
@@ -0,0 +1,457 @@
+# Module kotlinx-coroutines-test
+
+Test utilities for `kotlinx.coroutines`.
+
+This package provides testing utilities for effectively testing coroutines.
+
+## Using in your project
+
+Add `kotlinx-coroutines-test` to your project test dependencies:
+```
+dependencies {
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.1'
+}
+```
+
+**Do not** depend on this project in your main sources, all utilities are intended and designed to be used only from tests.
+
+## Dispatchers.Main Delegation
+
+`Dispatchers.setMain` will override the `Main` dispatcher in test situations. This is helpful when you want to execute a
+test in situations where the platform `Main` dispatcher is not available, or you wish to replace `Dispatchers.Main` with a
+testing dispatcher.
+
+Once you have this dependency in the runtime,
+[`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) mechanism will overwrite
+[Dispatchers.Main] with a testable implementation.
+
+You can override the `Main` implementation using [setMain][setMain] method with any [CoroutineDispatcher] implementation, e.g.:
+
+```kotlin
+
+class SomeTest {
+
+ private val mainThreadSurrogate = newSingleThreadContext("UI thread")
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(mainThreadSurrogate)
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
+ mainThreadSurrogate.close()
+ }
+
+ @Test
+ fun testSomeUI() = runBlocking {
+ launch(Dispatchers.Main) { // Will be launched in the mainThreadSurrogate dispatcher
+ // ...
+ }
+ }
+}
+```
+Calling `setMain` or `resetMain` immediately changes the `Main` dispatcher globally. The testable version of
+`Dispatchers.Main` installed by the `ServiceLoader` will delegate to the dispatcher provided by `setMain`.
+
+## runBlockingTest
+
+To test regular suspend functions or coroutines started with `launch` or `async` use the [runBlockingTest] coroutine
+builder that provides extra test control to coroutines.
+
+1. Auto-advancing of time for regular suspend functions
+2. Explicit time control for testing multiple coroutines
+3. Eager execution of `launch` or `async` code blocks
+4. Pause, manually advance, and restart the execution of coroutines in a test
+5. Report uncaught exceptions as test failures
+
+### Testing regular suspend functions
+
+To test regular suspend functions, which may have a delay, you can use the [runBlockingTest] builder to start a testing
+coroutine. Any calls to `delay` will automatically advance virtual time by the amount delayed.
+
+```kotlin
+@Test
+fun testFoo() = runBlockingTest { // a coroutine with an extra test control
+ val actual = foo()
+ // ...
+}
+
+suspend fun foo() {
+ delay(1_000) // auto-advances virtual time by 1_000ms due to runBlockingTest
+ // ...
+}
+```
+
+`runBlockingTest` returns `Unit` so it may be used in a single expression with common testing libraries.
+
+### Testing `launch` or `async`
+
+Inside of [runBlockingTest], both [launch] and [async] will start a new coroutine that may run concurrently with the
+test case.
+
+To make common testing situations easier, by default the body of the coroutine is executed *eagerly* until
+the first call to [delay] or [yield].
+
+```kotlin
+@Test
+fun testFooWithLaunch() = runBlockingTest {
+ foo()
+ // the coroutine launched by foo() is completed before foo() returns
+ // ...
+}
+
+fun CoroutineScope.foo() {
+ // This coroutines `Job` is not shared with the test code
+ launch {
+ bar() // executes eagerly when foo() is called due to runBlockingTest
+ println(1) // executes eagerly when foo() is called due to runBlockingTest
+ }
+}
+
+suspend fun bar() {}
+```
+
+`runBlockingTest` will auto-progress virtual time until all coroutines are completed before returning. If any coroutines
+are not able to complete, an [UncompletedCoroutinesError] will be thrown.
+
+*Note:* The default eager behavior of [runBlockingTest] will ignore [CoroutineStart] parameters.
+
+### Testing `launch` or `async` with `delay`
+
+If the coroutine created by `launch` or `async` calls `delay` then the [runBlockingTest] will not auto-progress time
+right away. This allows tests to observe the interaction of multiple coroutines with different delays.
+
+To control time in the test you can use the [DelayController] interface. The block passed to
+[runBlockingTest] can call any method on the `DelayController` interface.
+
+```kotlin
+@Test
+fun testFooWithLaunchAndDelay() = runBlockingTest {
+ foo()
+ // the coroutine launched by foo has not completed here, it is suspended waiting for delay(1_000)
+ advanceTimeBy(1_000) // progress time, this will cause the delay to resume
+ // the coroutine launched by foo has completed here
+ // ...
+}
+
+suspend fun CoroutineScope.foo() {
+ launch {
+ println(1) // executes eagerly when foo() is called due to runBlockingTest
+ delay(1_000) // suspends until time is advanced by at least 1_000
+ println(2) // executes after advanceTimeBy(1_000)
+ }
+}
+```
+
+*Note:* `runBlockingTest` will always attempt to auto-progress time until all coroutines are completed just before
+exiting. This is a convenience to avoid having to call [advanceUntilIdle][DelayController.advanceUntilIdle]
+as the last line of many common test cases.
+If any coroutines cannot complete by advancing time, an [UncompletedCoroutinesError] is thrown.
+
+### Testing `withTimeout` using `runBlockingTest`
+
+Time control can be used to test timeout code. To do so, ensure that the function under test is suspended inside a
+`withTimeout` block and advance time until the timeout is triggered.
+
+Depending on the code, causing the code to suspend may need to use different mocking or fake techniques. For this
+example an uncompleted `Deferred<Foo>` is provided to the function under test via parameter injection.
+
+```kotlin
+@Test(expected = TimeoutCancellationException::class)
+fun testFooWithTimeout() = runBlockingTest {
+ val uncompleted = CompletableDeferred<Foo>() // this Deferred<Foo> will never complete
+ foo(uncompleted)
+ advanceTimeBy(1_000) // advance time, which will cause the timeout to throw an exception
+ // ...
+}
+
+fun CoroutineScope.foo(resultDeferred: Deferred<Foo>) {
+ launch {
+ withTimeout(1_000) {
+ resultDeferred.await() // await() will suspend forever waiting for uncompleted
+ // ...
+ }
+ }
+}
+```
+
+*Note:* Testing timeouts is simpler with a second coroutine that can be suspended (as in this example). If the
+call to `withTimeout` is in a regular suspend function, consider calling `launch` or `async` inside your test body to
+create a second coroutine.
+
+### Using `pauseDispatcher` for explicit execution of `runBlockingTest`
+
+The eager execution of `launch` and `async` bodies makes many tests easier, but some tests need more fine grained
+control of coroutine execution.
+
+To disable eager execution, you can call [pauseDispatcher][DelayController.pauseDispatcher]
+to pause the [TestCoroutineDispatcher] that [runBlockingTest] uses.
+
+When the dispatcher is paused, all coroutines will be added to a queue instead running. In addition, time will never
+auto-progress due to `delay` on a paused dispatcher.
+
+```kotlin
+@Test
+fun testFooWithPauseDispatcher() = runBlockingTest {
+ pauseDispatcher {
+ foo()
+ // the coroutine started by foo has not run yet
+ runCurrent() // the coroutine started by foo advances to delay(1_000)
+ // the coroutine started by foo has called println(1), and is suspended on delay(1_000)
+ advanceTimeBy(1_000) // progress time, this will cause the delay to resume
+ // the coroutine started by foo has called println(2) and has completed here
+ }
+ // ...
+}
+
+fun CoroutineScope.foo() {
+ launch {
+ println(1) // executes after runCurrent() is called
+ delay(1_000) // suspends until time is advanced by at least 1_000
+ println(2) // executes after advanceTimeBy(1_000)
+ }
+}
+```
+
+Using `pauseDispatcher` gives tests explicit control over the progress of time as well as the ability to enqueue all
+coroutines. As a best practice consider adding two tests, one paused and one eager, to test coroutines that have
+non-trivial external dependencies and side effects in their launch body.
+
+*Important:* When passed a lambda block, `pauseDispatcher` will resume eager execution immediately after the block.
+This will cause time to auto-progress if there are any outstanding `delay` calls that were not resolved before the
+`pauseDispatcher` block returned. In advanced situations tests can call [pauseDispatcher][DelayController.pauseDispatcher]
+without a lambda block and then explicitly resume the dispatcher with [resumeDispatcher][DelayController.resumeDispatcher].
+
+## Integrating tests with structured concurrency
+
+Code that uses structured concurrency needs a [CoroutineScope] in order to launch a coroutine. In order to integrate
+[runBlockingTest] with code that uses common structured concurrency patterns tests can provide one (or both) of these
+classes to application code.
+
+ | Name | Description |
+ | ---- | ----------- |
+ | [TestCoroutineScope] | A [CoroutineScope] which provides detailed control over the execution of coroutines for tests and integrates with [runBlockingTest]. |
+ | [TestCoroutineDispatcher] | A [CoroutineDispatcher] which can be used for tests and integrates with [runBlockingTest]. |
+
+ Both classes are provided to allow for various testing needs. Depending on the code that's being
+ tested, it may be easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain] will accept
+ a [TestCoroutineDispatcher] but not a [TestCoroutineScope].
+
+ [TestCoroutineScope] will always use a [TestCoroutineDispatcher] to execute coroutines. It
+ also uses [TestCoroutineExceptionHandler] to convert uncaught exceptions into test failures.
+
+By providing [TestCoroutineScope] a test case is able to control execution of coroutines, as well as ensure that
+uncaught exceptions thrown by coroutines are converted into test failures.
+
+### Providing `TestCoroutineScope` from `runBlockingTest`
+
+In simple cases, tests can use the [TestCoroutineScope] created by [runBlockingTest] directly.
+
+```kotlin
+@Test
+fun testFoo() = runBlockingTest {
+ foo() // runBlockingTest passed in a TestCoroutineScope as this
+}
+
+fun CoroutineScope.foo() {
+ launch { // CoroutineScope for launch is the TestCoroutineScope provided by runBlockingTest
+ // ...
+ }
+}
+```
+
+This style is preferred when the `CoroutineScope` is passed through an extension function style.
+
+### Providing an explicit `TestCoroutineScope`
+
+In many cases, the direct style is not preferred because [CoroutineScope] may need to be provided through another means
+such as dependency injection or service locators.
+
+Tests can declare a [TestCoroutineScope] explicitly in the class to support these use cases.
+
+Since [TestCoroutineScope] is stateful in order to keep track of executing coroutines and uncaught exceptions, it is
+important to ensure that [cleanupTestCoroutines][TestCoroutineScope.cleanupTestCoroutines] is called after every test case.
+
+```kotlin
+class TestClass {
+ private val testScope = TestCoroutineScope()
+ private lateinit var subject: Subject = null
+
+ @Before
+ fun setup() {
+ // provide the scope explicitly, in this example using a constructor parameter
+ subject = Subject(testScope)
+ }
+
+ @After
+ fun cleanUp() {
+ testScope.cleanupTestCoroutines()
+ }
+
+ @Test
+ fun testFoo() = testScope.runBlockingTest {
+ // TestCoroutineScope.runBlockingTest uses the Dispatcher and exception handler provided by `testScope`
+ subject.foo()
+ }
+}
+
+class Subject(val scope: CoroutineScope) {
+ fun foo() {
+ scope.launch {
+ // launch uses the testScope injected in setup
+ }
+ }
+}
+```
+
+*Note:* [TestCoroutineScope], [TestCoroutineDispatcher], and [TestCoroutineExceptionHandler] are interfaces to enable
+test libraries to provide library specific integrations. For example, a JUnit4 `@Rule` may call
+[Dispatchers.setMain][setMain] then expose [TestCoroutineScope] for use in tests.
+
+### Providing an explicit `TestCoroutineDispatcher`
+
+While providing a [TestCoroutineScope] is slightly preferred due to the improved uncaught exception handling, there are
+many situations where it is easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain]
+does not accept a [TestCoroutineScope] and requires a [TestCoroutineDispatcher] to control coroutine execution in
+tests.
+
+The main difference between `TestCoroutineScope` and `TestCoroutineDispatcher` is how uncaught exceptions are handled.
+When using `TestCoroutineDispatcher` uncaught exceptions thrown in coroutines will use regular
+[coroutine exception handling](https://kotlinlang.org/docs/reference/coroutines/exception-handling.html).
+`TestCoroutineScope` will always use `TestCoroutineDispatcher` as it's dispatcher.
+
+A test can use a `TestCoroutineDispatcher` without declaring an explicit `TestCoroutineScope`. This is preferred
+when the class under test allows a test to provide a [CoroutineDispatcher] but does not allow the test to provide a
+[CoroutineScope].
+
+Since [TestCoroutineDispatcher] is stateful in order to keep track of executing coroutines, it is
+important to ensure that [cleanupTestCoroutines][DelayController.cleanupTestCoroutines] is called after every test case.
+
+```kotlin
+class TestClass {
+ private val testDispatcher = TestCoroutineDispatcher()
+
+ @Before
+ fun setup() {
+ // provide the scope explicitly, in this example using a constructor parameter
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ @After
+ fun cleanUp() {
+ Dispatchers.resetMain()
+ testDispatcher.cleanupTestCoroutines()
+ }
+
+ @Test
+ fun testFoo() = testDispatcher.runBlockingTest {
+ // TestCoroutineDispatcher.runBlockingTest uses `testDispatcher` to run coroutines
+ foo()
+ }
+}
+
+fun foo() {
+ MainScope().launch {
+ // launch will use the testDispatcher provided by setMain
+ }
+}
+```
+
+*Note:* Prefer to provide `TestCoroutineScope` when it does not complicate code since it will also elevate exceptions
+to test failures. However, exposing a `CoroutineScope` to callers of a function may lead to complicated code, in which
+case this is the preferred pattern.
+
+### Using `TestCoroutineScope` and `TestCoroutineDispatcher` without `runBlockingTest`
+
+It is supported to use both [TestCoroutineScope] and [TestCoroutineDispatcher] without using the [runBlockingTest]
+builder. Tests may need to do this in situations such as introducing multiple dispatchers and library writers may do
+this to provide alternatives to `runBlockingTest`.
+
+```kotlin
+@Test
+fun testFooWithAutoProgress() {
+ val scope = TestCoroutineScope()
+ scope.foo()
+ // foo is suspended waiting for time to progress
+ scope.advanceUntilIdle()
+ // foo's coroutine will be completed before here
+}
+
+fun CoroutineScope.foo() {
+ launch {
+ println(1) // executes eagerly when foo() is called due to TestCoroutineScope
+ delay(1_000) // suspends until time is advanced by at least 1_000
+ println(2) // executes after advanceTimeUntilIdle
+ }
+}
+```
+
+## Using time control with `withContext`
+
+Calls to `withContext(Dispatchers.IO)` or `withContext(Dispatchers.Default)` are common in coroutines based codebases.
+Both dispatchers are not designed to interact with `TestCoroutineDispatcher`.
+
+Tests should provide a `TestCoroutineDispatcher` to replace these dispatchers if the `withContext` calls `delay` in the
+function under test. For example, a test that calls `veryExpensiveOne` should provide a `TestCoroutineDispatcher` using
+either dependency injection, a service locator, or a default parameter.
+
+```kotlin
+suspend fun veryExpensiveOne() = withContext(Dispatchers.Default) {
+ delay(1_000)
+ 1 // for very expensive values of 1
+}
+```
+
+In situations where the code inside the `withContext` is very simple, it is not as important to provide a test
+dispatcher. The function `veryExpensiveTwo` will behave identically in a `TestCoroutineDispatcher` and
+`Dispatchers.Default` after the thread switch for `Dispatchers.Default`. Because `withContext` always returns a value by
+directly, there is no need to inject a `TestCoroutineDispatcher` into this function.
+
+```kotlin
+suspend fun veryExpensiveTwo() = withContext(Dispatchers.Default) {
+ 2 // for very expensive values of 2
+}
+```
+
+Tests should provide a `TestCoroutineDispatcher` to code that calls `withContext` to provide time control for
+delays, or when execution control is needed to test complex logic.
+
+
+### Status of the API
+
+This API is experimental and it is may change before migrating out of experimental (while it is marked as
+[`@ExperimentalCoroutinesApi`][ExperimentalCoroutinesApi]).
+Changes during experimental may have deprecation applied when possible, but it is not
+advised to use the API in stable code before it leaves experimental due to possible breaking changes.
+
+If you have any suggestions for improvements to this experimental API please share them them on the
+[issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues).
+
+<!--- 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
+[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.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
+[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
+[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
+[DelayController.advanceUntilIdle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/advance-until-idle.html
+[DelayController.pauseDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/pause-dispatcher.html
+[TestCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-dispatcher/index.html
+[DelayController.resumeDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/resume-dispatcher.html
+[TestCoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/index.html
+[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 b/kotlinx-coroutines-test/build.gradle
new file mode 100644
index 00000000..e13946fb
--- /dev/null
+++ b/kotlinx-coroutines-test/build.gradle
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 00000000..41c9eb07
--- /dev/null
+++ b/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro
@@ -0,0 +1,9 @@
+# ServiceLoader support
+-keepnames class kotlinx.coroutines.test.internal.TestMainDispatcherFactory {}
+-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
+-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
+
+# Most of volatile fields are updated with AFU and should not be mangled
+-keepclassmembernames class kotlinx.** {
+ volatile <fields>;
+}
diff --git a/kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory b/kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
new file mode 100644
index 00000000..0ec0c9d5
--- /dev/null
+++ b/kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
@@ -0,0 +1 @@
+kotlinx.coroutines.test.internal.TestMainDispatcherFactory
diff --git a/kotlinx-coroutines-test/src/DelayController.kt b/kotlinx-coroutines-test/src/DelayController.kt
new file mode 100644
index 00000000..54e9c8ae
--- /dev/null
+++ b/kotlinx-coroutines-test/src/DelayController.kt
@@ -0,0 +1,125 @@
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Control the virtual clock time of a [CoroutineDispatcher].
+ *
+ * Testing libraries may expose this interface to tests instead of [TestCoroutineDispatcher].
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public interface DelayController {
+ /**
+ * Returns the current virtual clock-time as it is known to this Dispatcher.
+ *
+ * @return The virtual clock-time
+ */
+ @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+ public val currentTime: Long
+
+ /**
+ * Moves the Dispatcher's virtual clock forward by a specified amount of time.
+ *
+ * The amount the clock is progressed may be larger than the requested `delayTimeMillis` if the code under test uses
+ * blocking coroutines.
+ *
+ * The virtual clock time will advance once for each delay resumed until the next delay exceeds the requested
+ * `delayTimeMills`. In the following test, the virtual time will progress by 2_000 then 1 to resume three different
+ * calls to delay.
+ *
+ * ```
+ * @Test
+ * fun advanceTimeTest() = runBlockingTest {
+ * foo()
+ * advanceTimeBy(2_000) // advanceTimeBy(2_000) will progress through the first two delays
+ * // virtual time is 2_000, next resume is at 2_001
+ * advanceTimeBy(2) // progress through the last delay of 501 (note 500ms were already advanced)
+ * // virtual time is 2_0002
+ * }
+ *
+ * fun CoroutineScope.foo() {
+ * launch {
+ * delay(1_000) // advanceTimeBy(2_000) will progress through this delay (resume @ virtual time 1_000)
+ * // virtual time is 1_000
+ * delay(500) // advanceTimeBy(2_000) will progress through this delay (resume @ virtual time 1_500)
+ * // virtual time is 1_500
+ * delay(501) // advanceTimeBy(2_000) will not progress through this delay (resume @ virtual time 2_001)
+ * // virtual time is 2_001
+ * }
+ * }
+ * ```
+ *
+ * @param delayTimeMillis The amount of time to move the CoroutineContext's clock forward.
+ * @return The amount of delay-time that this Dispatcher's clock has been forwarded.
+ */
+ @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+ public fun advanceTimeBy(delayTimeMillis: Long): Long
+
+ /**
+ * Immediately execute all pending tasks and advance the virtual clock-time to the last delay.
+ *
+ * If new tasks are scheduled due to advancing virtual time, they will be executed before `advanceUntilIdle`
+ * returns.
+ *
+ * @return the amount of delay-time that this Dispatcher's clock has been forwarded in milliseconds.
+ */
+ @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+ public fun advanceUntilIdle(): Long
+
+ /**
+ * Run any tasks that are pending at or before the current virtual clock-time.
+ *
+ * Calling this function will never advance the clock.
+ */
+ @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+ public fun runCurrent()
+
+ /**
+ * Call after test code completes to ensure that the dispatcher is properly cleaned up.
+ *
+ * @throws UncompletedCoroutinesError if any pending tasks are active, however it will not throw for suspended
+ * coroutines.
+ */
+ @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+ @Throws(UncompletedCoroutinesError::class)
+ public fun cleanupTestCoroutines()
+
+ /**
+ * Run a block of code in a paused dispatcher.
+ *
+ * By pausing the dispatcher any new coroutines will not execute immediately. After block executes, the dispatcher
+ * will resume auto-advancing.
+ *
+ * This is useful when testing functions that start a coroutine. By pausing the dispatcher assertions or
+ * setup may be done between the time the coroutine is created and started.
+ */
+ @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+ public suspend fun pauseDispatcher(block: suspend () -> Unit)
+
+ /**
+ * Pause the dispatcher.
+ *
+ * When paused, the dispatcher will not execute any coroutines automatically, and you must call [runCurrent] or
+ * [advanceTimeBy], or [advanceUntilIdle] to execute coroutines.
+ */
+ @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+ public fun pauseDispatcher()
+
+ /**
+ * Resume the dispatcher from a paused state.
+ *
+ * Resumed dispatchers will automatically progress through all coroutines scheduled at the current time. To advance
+ * time and execute coroutines scheduled in the future use, one of [advanceTimeBy],
+ * or [advanceUntilIdle].
+ */
+ @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+ public fun resumeDispatcher()
+}
+
+/**
+ * Thrown when a test has completed and there are tasks that are not completed or cancelled.
+ */
+// todo: maybe convert into non-public class in 1.3.0 (need use-cases for a public exception type)
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public class UncompletedCoroutinesError(message: String, cause: Throwable? = null): AssertionError(message, cause)
diff --git a/kotlinx-coroutines-test/src/TestBuilders.kt b/kotlinx-coroutines-test/src/TestBuilders.kt
new file mode 100644
index 00000000..7ef77bd6
--- /dev/null
+++ b/kotlinx-coroutines-test/src/TestBuilders.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+
+/**
+ * Executes a [testBody] inside an immediate execution dispatcher.
+ *
+ * This is similar to [runBlocking] but it will immediately progress past delays and into [launch] and [async] blocks.
+ * You can use this to write tests that execute in the presence of calls to [delay] without causing your test to take
+ * extra time.
+ *
+ * ```
+ * @Test
+ * fun exampleTest() = runBlockingTest {
+ * val deferred = async {
+ * delay(1_000)
+ * async {
+ * delay(1_000)
+ * }.await()
+ * }
+ *
+ * deferred.await() // result available immediately
+ * }
+ *
+ * ```
+ *
+ * This method requires that all coroutines launched inside [testBody] complete, or are cancelled, as part of the test
+ * conditions.
+ *
+ * Unhandled exceptions thrown by coroutines in the test will be re-thrown at the end of the test.
+ *
+ * @throws UncompletedCoroutinesError If the [testBody] does not complete (or cancel) all coroutines that it launches
+ * (including coroutines suspended on join/await).
+ *
+ * @param context additional context elements. If [context] contains [CoroutineDispatcher] or [CoroutineExceptionHandler],
+ * then they must implement [DelayController] and [TestCoroutineExceptionHandler] respectively.
+ * @param testBody The code of the unit-test.
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public fun runBlockingTest(context: CoroutineContext = EmptyCoroutineContext, testBody: suspend TestCoroutineScope.() -> Unit) {
+ val (safeContext, dispatcher) = context.checkArguments()
+ val startingJobs = safeContext.activeJobs()
+ val scope = TestCoroutineScope(safeContext)
+ val deferred = scope.async {
+ scope.testBody()
+ }
+ dispatcher.advanceUntilIdle()
+ deferred.getCompletionExceptionOrNull()?.let {
+ throw it
+ }
+ scope.cleanupTestCoroutines()
+ val endingJobs = safeContext.activeJobs()
+ if ((endingJobs - startingJobs).isNotEmpty()) {
+ throw UncompletedCoroutinesError("Test finished with active jobs: $endingJobs")
+ }
+}
+
+private fun CoroutineContext.activeJobs(): Set<Job> {
+ return checkNotNull(this[Job]).children.filter { it.isActive }.toSet()
+}
+
+/**
+ * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineScope].
+ */
+// todo: need documentation on how this extension is supposed to be used
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public fun TestCoroutineScope.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = runBlockingTest(coroutineContext, block)
+
+/**
+ * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineDispatcher].
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = runBlockingTest(this, block)
+
+private fun CoroutineContext.checkArguments(): Pair<CoroutineContext, DelayController> {
+ // TODO optimize it
+ val dispatcher = get(ContinuationInterceptor).run {
+ this?.let { require(this is DelayController) { "Dispatcher must implement DelayController: $this" } }
+ this ?: TestCoroutineDispatcher()
+ }
+
+ val exceptionHandler = get(CoroutineExceptionHandler).run {
+ this?.let {
+ require(this is UncaughtExceptionCaptor) { "coroutineExceptionHandler must implement UncaughtExceptionCaptor: $this" }
+ }
+ this ?: TestCoroutineExceptionHandler()
+ }
+
+ val job = get(Job) ?: SupervisorJob()
+ return Pair(this + dispatcher + exceptionHandler + job, dispatcher as DelayController)
+}
diff --git a/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt b/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt
new file mode 100644
index 00000000..386fc838
--- /dev/null
+++ b/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.math.*
+
+/**
+ * [CoroutineDispatcher] that performs both immediate and lazy execution of coroutines in tests
+ * and implements [DelayController] to control its virtual clock.
+ *
+ * By default, [TestCoroutineDispatcher] is immediate. That means any tasks scheduled to be run without delay are
+ * immediately executed. If they were scheduled with a delay, the virtual clock-time must be advanced via one of the
+ * methods on [DelayController].
+ *
+ * When switched to lazy execution using [pauseDispatcher] any coroutines started via [launch] or [async] will
+ * not execute until a call to [DelayController.runCurrent] or the virtual clock-time has been advanced via one of the
+ * methods on [DelayController].
+ *
+ * @see DelayController
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public class TestCoroutineDispatcher: CoroutineDispatcher(), Delay, DelayController {
+ private var dispatchImmediately = true
+ set(value) {
+ field = value
+ if (value) {
+ // there may already be tasks from setup code we need to run
+ advanceUntilIdle()
+ }
+ }
+
+ // The ordered queue for the runnable tasks.
+ private val queue = ThreadSafeHeap<TimedRunnable>()
+
+ // The per-scheduler global order counter.
+ private val _counter = atomic(0L)
+
+ // Storing time in nanoseconds internally.
+ private val _time = atomic(0L)
+
+ /** @suppress */
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ if (dispatchImmediately) {
+ block.run()
+ } else {
+ post(block)
+ }
+ }
+
+ /** @suppress */
+ @InternalCoroutinesApi
+ override fun dispatchYield(context: CoroutineContext, block: Runnable) {
+ post(block)
+ }
+
+ /** @suppress */
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ postDelayed(CancellableContinuationRunnable(continuation) { resumeUndispatched(Unit) }, timeMillis)
+ }
+
+ /** @suppress */
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val node = postDelayed(block, timeMillis)
+ return object : DisposableHandle {
+ override fun dispose() {
+ queue.remove(node)
+ }
+ }
+ }
+
+ /** @suppress */
+ override fun toString(): String {
+ return "TestCoroutineDispatcher[currentTime=${currentTime}ms, queued=${queue.size}]"
+ }
+
+ private fun post(block: Runnable) =
+ queue.addLast(TimedRunnable(block, _counter.getAndIncrement()))
+
+ private fun postDelayed(block: Runnable, delayTime: Long) =
+ TimedRunnable(block, _counter.getAndIncrement(), safePlus(currentTime, delayTime))
+ .also {
+ queue.addLast(it)
+ }
+
+ private fun safePlus(currentTime: Long, delayTime: Long): Long {
+ check(delayTime >= 0)
+ val result = currentTime + delayTime
+ if (result < currentTime) return Long.MAX_VALUE // clam on overflow
+ return result
+ }
+
+ private fun doActionsUntil(targetTime: Long) {
+ while (true) {
+ val current = queue.removeFirstIf { it.time <= targetTime } ?: break
+ // If the scheduled time is 0 (immediate) use current virtual time
+ if (current.time != 0L) _time.value = current.time
+ current.run()
+ }
+ }
+
+ /** @suppress */
+ override val currentTime get() = _time.value
+
+ /** @suppress */
+ override fun advanceTimeBy(delayTimeMillis: Long): Long {
+ val oldTime = currentTime
+ advanceUntilTime(oldTime + delayTimeMillis)
+ return currentTime - oldTime
+ }
+
+ /**
+ * Moves the CoroutineContext's clock-time to a particular moment in time.
+ *
+ * @param targetTime The point in time to which to move the CoroutineContext's clock (milliseconds).
+ */
+ private fun advanceUntilTime(targetTime: Long) {
+ doActionsUntil(targetTime)
+ _time.update { currentValue -> max(currentValue, targetTime) }
+ }
+
+ /** @suppress */
+ override fun advanceUntilIdle(): Long {
+ val oldTime = currentTime
+ while(!queue.isEmpty) {
+ runCurrent()
+ val next = queue.peek() ?: break
+ advanceUntilTime(next.time)
+ }
+ return currentTime - oldTime
+ }
+
+ /** @suppress */
+ override fun runCurrent() = doActionsUntil(currentTime)
+
+ /** @suppress */
+ override suspend fun pauseDispatcher(block: suspend () -> Unit) {
+ val previous = dispatchImmediately
+ dispatchImmediately = false
+ try {
+ block()
+ } finally {
+ dispatchImmediately = previous
+ }
+ }
+
+ /** @suppress */
+ override fun pauseDispatcher() {
+ dispatchImmediately = false
+ }
+
+ /** @suppress */
+ override fun resumeDispatcher() {
+ dispatchImmediately = true
+ }
+
+ /** @suppress */
+ override fun cleanupTestCoroutines() {
+ // process any pending cancellations or completions, but don't advance time
+ doActionsUntil(currentTime)
+
+ // run through all pending tasks, ignore any submitted coroutines that are not active
+ val pendingTasks = mutableListOf<TimedRunnable>()
+ while (true) {
+ pendingTasks += queue.removeFirstOrNull() ?: break
+ }
+ val activeDelays = pendingTasks
+ .mapNotNull { it.runnable as? CancellableContinuationRunnable<*> }
+ .filter { it.continuation.isActive }
+
+ val activeTimeouts = pendingTasks.filter { it.runnable !is CancellableContinuationRunnable<*> }
+ if (activeDelays.isNotEmpty() || activeTimeouts.isNotEmpty()) {
+ throw UncompletedCoroutinesError(
+ "Unfinished coroutines during teardown. Ensure all coroutines are" +
+ " completed or cancelled by your test."
+ )
+ }
+ }
+}
+
+/**
+ * This class exists to allow cleanup code to avoid throwing for cancelled continuations scheduled
+ * in the future.
+ */
+private class CancellableContinuationRunnable<T>(
+ @JvmField val continuation: CancellableContinuation<T>,
+ private val block: CancellableContinuation<T>.() -> Unit
+) : Runnable {
+ override fun run() = continuation.block()
+}
+
+/**
+ * A Runnable for our event loop that represents a task to perform at a time.
+ */
+private class TimedRunnable(
+ @JvmField val runnable: Runnable,
+ private val count: Long = 0,
+ @JvmField val time: Long = 0
+) : Comparable<TimedRunnable>, Runnable by runnable, ThreadSafeHeapNode {
+ override var heap: ThreadSafeHeap<*>? = null
+ override var index: Int = 0
+
+ override fun compareTo(other: TimedRunnable) = if (time == other.time) {
+ count.compareTo(other.count)
+ } else {
+ time.compareTo(other.time)
+ }
+
+ override fun toString() = "TimedRunnable(time=$time, run=$runnable)"
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt
new file mode 100644
index 00000000..41d14d08
--- /dev/null
+++ b/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+
+/**
+ * Access uncaught coroutine exceptions captured during test execution.
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public interface UncaughtExceptionCaptor {
+ /**
+ * List of uncaught coroutine exceptions.
+ *
+ * The returned list is a copy of the currently caught exceptions.
+ * During [cleanupTestCoroutines] the first element of this list is rethrown if it is not empty.
+ */
+ public val uncaughtExceptions: List<Throwable>
+
+ /**
+ * Call after the test completes to ensure that there were no uncaught exceptions.
+ *
+ * The first exception in uncaughtExceptions is rethrown. All other exceptions are
+ * printed using [Throwable.printStackTrace].
+ *
+ * @throws Throwable the first uncaught exception, if there are any uncaught exceptions.
+ */
+ public fun cleanupTestCoroutines()
+}
+
+/**
+ * An exception handler that captures uncaught exceptions in tests.
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public class TestCoroutineExceptionHandler :
+ AbstractCoroutineContextElement(CoroutineExceptionHandler), UncaughtExceptionCaptor, CoroutineExceptionHandler
+{
+ private val _exceptions = mutableListOf<Throwable>()
+
+ /** @suppress **/
+ override fun handleException(context: CoroutineContext, exception: Throwable) {
+ synchronized(_exceptions) {
+ _exceptions += exception
+ }
+ }
+
+ /** @suppress **/
+ override val uncaughtExceptions
+ get() = synchronized(_exceptions) { _exceptions.toList() }
+
+ /** @suppress **/
+ override fun cleanupTestCoroutines() {
+ synchronized(_exceptions) {
+ val exception = _exceptions.firstOrNull() ?: return
+ // log the rest
+ _exceptions.drop(1).forEach { it.printStackTrace() }
+ throw exception
+ }
+ }
+}
diff --git a/kotlinx-coroutines-test/src/TestCoroutineScope.kt b/kotlinx-coroutines-test/src/TestCoroutineScope.kt
new file mode 100644
index 00000000..d39e6adf
--- /dev/null
+++ b/kotlinx-coroutines-test/src/TestCoroutineScope.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+
+/**
+ * A scope which provides detailed control over the execution of coroutines for tests.
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public interface TestCoroutineScope: CoroutineScope, UncaughtExceptionCaptor, DelayController {
+ /**
+ * Call after the test completes.
+ * Calls [UncaughtExceptionCaptor.cleanupTestCoroutines] and [DelayController.cleanupTestCoroutines].
+ *
+ * @throws Throwable the first uncaught exception, if there are any uncaught exceptions.
+ * @throws UncompletedCoroutinesError if any pending tasks are active, however it will not throw for suspended
+ * coroutines.
+ */
+ public override fun cleanupTestCoroutines()
+}
+
+private class TestCoroutineScopeImpl (
+ override val coroutineContext: CoroutineContext
+):
+ TestCoroutineScope,
+ UncaughtExceptionCaptor by coroutineContext.uncaughtExceptionCaptor,
+ DelayController by coroutineContext.delayController
+{
+ override fun cleanupTestCoroutines() {
+ coroutineContext.uncaughtExceptionCaptor.cleanupTestCoroutines()
+ coroutineContext.delayController.cleanupTestCoroutines()
+ }
+}
+
+/**
+ * A scope which provides detailed control over the execution of coroutines for tests.
+ *
+ * If the provided context does not provide a [ContinuationInterceptor] (Dispatcher) or [CoroutineExceptionHandler], the
+ * scope adds [TestCoroutineDispatcher] and [TestCoroutineExceptionHandler] automatically.
+ *
+ * @param context an optional context that MAY provide [UncaughtExceptionCaptor] and/or [DelayController]
+ */
+@Suppress("FunctionName")
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public fun TestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext): TestCoroutineScope {
+ var safeContext = context
+ if (context[ContinuationInterceptor] == null) safeContext += TestCoroutineDispatcher()
+ if (context[CoroutineExceptionHandler] == null) safeContext += TestCoroutineExceptionHandler()
+ return TestCoroutineScopeImpl(safeContext)
+}
+
+private inline val CoroutineContext.uncaughtExceptionCaptor: UncaughtExceptionCaptor
+ get() {
+ val handler = this[CoroutineExceptionHandler]
+ return handler as? UncaughtExceptionCaptor ?: throw IllegalArgumentException(
+ "TestCoroutineScope requires a UncaughtExceptionCaptor such as " +
+ "TestCoroutineExceptionHandler as the CoroutineExceptionHandler"
+ )
+ }
+
+private inline val CoroutineContext.delayController: DelayController
+ get() {
+ val handler = this[ContinuationInterceptor]
+ return handler as? DelayController ?: throw IllegalArgumentException(
+ "TestCoroutineScope requires a DelayController such as TestCoroutineDispatcher as " +
+ "the ContinuationInterceptor (Dispatcher)"
+ )
+ } \ No newline at end of file
diff --git a/kotlinx-coroutines-test/src/TestDispatchers.kt b/kotlinx-coroutines-test/src/TestDispatchers.kt
new file mode 100644
index 00000000..532db938
--- /dev/null
+++ b/kotlinx-coroutines-test/src/TestDispatchers.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("unused")
+@file:JvmName("TestDispatchers")
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.internal.*
+
+/**
+ * Sets the given [dispatcher] as an underlying dispatcher of [Dispatchers.Main].
+ * All consecutive usages of [Dispatchers.Main] will use given [dispatcher] under the hood.
+ *
+ * It is unsafe to call this method if alive coroutines launched in [Dispatchers.Main] exist.
+ */
+@ExperimentalCoroutinesApi
+public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) {
+ require(dispatcher !is TestMainDispatcher) { "Dispatchers.setMain(Dispatchers.Main) is prohibited, probably Dispatchers.resetMain() should be used instead" }
+ val mainDispatcher = Dispatchers.Main
+ require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." }
+ mainDispatcher.setDispatcher(dispatcher)
+}
+
+/**
+ * Resets state of the [Dispatchers.Main] to the original main dispatcher.
+ * For example, in Android Main thread dispatcher will be set as [Dispatchers.Main].
+ * Used to clean up all possible dependencies, should be used in tear down (`@After`) methods.
+ *
+ * It is unsafe to call this method if alive coroutines launched in [Dispatchers.Main] exist.
+ */
+@ExperimentalCoroutinesApi
+public fun Dispatchers.resetMain() {
+ val mainDispatcher = Dispatchers.Main
+ require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." }
+ mainDispatcher.resetDispatcher()
+}
diff --git a/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt b/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt
new file mode 100644
index 00000000..bb52999d
--- /dev/null
+++ b/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+
+/**
+ * The testable main dispatcher used by kotlinx-coroutines-test.
+ * It is a [MainCoroutineDispatcher] which delegates all actions to a settable delegate.
+ */
+internal class TestMainDispatcher(private val mainFactory: MainDispatcherFactory) : MainCoroutineDispatcher(), Delay {
+ private var _delegate: CoroutineDispatcher? = null
+ private val delegate: CoroutineDispatcher get() {
+ _delegate?.let { return it }
+ mainFactory.tryCreateDispatcher(emptyList()).let {
+ // If we've failed to create a dispatcher, do no set _delegate
+ if (!isMissing()) {
+ _delegate = it
+ }
+ return it
+ }
+ }
+
+ @Suppress("INVISIBLE_MEMBER")
+ private val delay: Delay get() = delegate as? Delay ?: DefaultDelay
+
+ override val immediate: MainCoroutineDispatcher
+ get() = (delegate as? MainCoroutineDispatcher)?.immediate ?: this
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ delegate.dispatch(context, block)
+ }
+
+ @ExperimentalCoroutinesApi
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean = delegate.isDispatchNeeded(context)
+
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ delay.scheduleResumeAfterDelay(timeMillis, continuation)
+ }
+
+ override suspend fun delay(time: Long) {
+ delay.delay(time)
+ }
+
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ return delay.invokeOnTimeout(timeMillis, block)
+ }
+
+ public fun setDispatcher(dispatcher: CoroutineDispatcher) {
+ _delegate = dispatcher
+ }
+
+ public fun resetDispatcher() {
+ _delegate = null
+ }
+}
+
+internal class TestMainDispatcherFactory : MainDispatcherFactory {
+
+ override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
+ val originalFactory = allFactories.asSequence()
+ .filter { it !== this }
+ .maxBy { it.loadPriority } ?: MissingMainCoroutineDispatcherFactory
+ return TestMainDispatcher(originalFactory)
+ }
+
+ /**
+ * [Int.MAX_VALUE] -- test dispatcher always wins no matter what factories are present in the classpath.
+ * By default all actions are delegated to the second-priority dispatcher, so that it won't be the issue.
+ */
+ override val loadPriority: Int
+ get() = Int.MAX_VALUE
+}
diff --git a/kotlinx-coroutines-test/test/TestBuildersTest.kt b/kotlinx-coroutines-test/test/TestBuildersTest.kt
new file mode 100644
index 00000000..1f163eef
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestBuildersTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Assert.*
+import kotlin.coroutines.*
+
+class TestBuildersTest {
+
+ @Test
+ fun scopeRunBlocking_passesDispatcher() {
+ val scope = TestCoroutineScope()
+ scope.runBlockingTest {
+ assertSame(scope.coroutineContext[ContinuationInterceptor], coroutineContext[ContinuationInterceptor])
+ }
+ }
+
+ @Test
+ fun dispatcherRunBlocking_passesDispatcher() {
+ val dispatcher = TestCoroutineDispatcher()
+ dispatcher.runBlockingTest {
+ assertSame(dispatcher, coroutineContext[ContinuationInterceptor])
+ }
+ }
+
+ @Test
+ fun scopeRunBlocking_advancesPreviousDelay() {
+ val scope = TestCoroutineScope()
+ val deferred = scope.async {
+ delay(SLOW)
+ 3
+ }
+
+ scope.runBlockingTest {
+ assertRunsFast {
+ assertEquals(3, deferred.await())
+ }
+ }
+ }
+
+ @Test
+ fun dispatcherRunBlocking_advancesPreviousDelay() {
+ val dispatcher = TestCoroutineDispatcher()
+ val scope = CoroutineScope(dispatcher)
+ val deferred = scope.async {
+ delay(SLOW)
+ 3
+ }
+
+ dispatcher.runBlockingTest {
+ assertRunsFast {
+ assertEquals(3, deferred.await())
+ }
+ }
+ }
+
+ @Test
+ fun scopeRunBlocking_disablesImmedateOnExit() {
+ val scope = TestCoroutineScope()
+ scope.runBlockingTest {
+ assertRunsFast {
+ delay(SLOW)
+ }
+ }
+
+ val deferred = scope.async {
+ delay(SLOW)
+ 3
+ }
+ scope.runCurrent()
+ assertTrue(deferred.isActive)
+
+ scope.advanceUntilIdle()
+ assertEquals(3, deferred.getCompleted())
+ }
+
+ @Test
+ fun whenInAsync_runBlocking_nestsProperly() {
+ // this is not a supported use case, but it is possible so ensure it works
+
+ val dispatcher = TestCoroutineDispatcher()
+ val scope = TestCoroutineScope(dispatcher)
+ val deferred = scope.async {
+ delay(1_000)
+ var retval = 2
+ runBlockingTest {
+ delay(1_000)
+ retval++
+ }
+ retval
+ }
+
+ scope.advanceTimeBy(1_000)
+ scope.launch {
+ assertRunsFast {
+ assertEquals(3, deferred.getCompleted())
+ }
+ }
+ scope.runCurrent() // execute the launch without changing to immediate dispatch (testing internals)
+ scope.cleanupTestCoroutines()
+ }
+
+ @Test
+ fun whenInrunBlocking_runBlockingTest_nestsProperly() {
+ // this is not a supported use case, but it is possible so ensure it works
+
+ val scope = TestCoroutineScope()
+ var calls = 0
+
+ scope.runBlockingTest {
+ delay(1_000)
+ calls++
+ runBlockingTest {
+ val job = launch {
+ delay(1_000)
+ calls++
+ }
+ assertTrue(job.isActive)
+ advanceUntilIdle()
+ assertFalse(job.isActive)
+ calls++
+ }
+ ++calls
+ }
+
+ assertEquals(4, calls)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt b/kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt
new file mode 100644
index 00000000..116aadcf
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt
@@ -0,0 +1,39 @@
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import org.junit.*
+import kotlin.coroutines.*
+import kotlin.test.assertEquals
+
+class TestCoroutineDispatcherOrderTest : TestBase() {
+
+ @Test
+ fun testAdvanceTimeBy_progressesOnEachDelay() {
+ val dispatcher = TestCoroutineDispatcher()
+ val scope = TestCoroutineScope(dispatcher)
+
+ expect(1)
+ scope.launch {
+ expect(2)
+ delay(1_000)
+ assertEquals(1_000, dispatcher.currentTime)
+ expect(4)
+ delay(5_00)
+ assertEquals(1_500, dispatcher.currentTime)
+ expect(5)
+ delay(501)
+ assertEquals(2_001, dispatcher.currentTime)
+ expect(7)
+ }
+ expect(3)
+ assertEquals(0, dispatcher.currentTime)
+ dispatcher.advanceTimeBy(2_000)
+ expect(6)
+ assertEquals(2_000, dispatcher.currentTime)
+ dispatcher.advanceTimeBy(2)
+ expect(8)
+ assertEquals(2_002, dispatcher.currentTime)
+ scope.cleanupTestCoroutines()
+ finish(9)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt b/kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt
new file mode 100644
index 00000000..260edf9d
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class TestCoroutineDispatcherTest {
+ @Test
+ fun whenStringCalled_itReturnsString() {
+ val subject = TestCoroutineDispatcher()
+ assertEquals("TestCoroutineDispatcher[currentTime=0ms, queued=0]", subject.toString())
+ }
+
+ @Test
+ fun whenStringCalled_itReturnsCurrentTime() {
+ val subject = TestCoroutineDispatcher()
+ subject.advanceTimeBy(1000)
+ assertEquals("TestCoroutineDispatcher[currentTime=1000ms, queued=0]", subject.toString())
+ }
+
+ @Test
+ fun whenStringCalled_itShowsQueuedJobs() {
+ val subject = TestCoroutineDispatcher()
+ val scope = TestCoroutineScope(subject)
+ scope.pauseDispatcher()
+ scope.launch {
+ delay(1_000)
+ }
+ assertEquals("TestCoroutineDispatcher[currentTime=0ms, queued=1]", subject.toString())
+ scope.advanceTimeBy(50)
+ assertEquals("TestCoroutineDispatcher[currentTime=50ms, queued=1]", subject.toString())
+ scope.advanceUntilIdle()
+ assertEquals("TestCoroutineDispatcher[currentTime=1000ms, queued=0]", subject.toString())
+ }
+
+ @Test
+ fun whenDispatcherPaused_doesntAutoProgressCurrent() {
+ val subject = TestCoroutineDispatcher()
+ subject.pauseDispatcher()
+ val scope = CoroutineScope(subject)
+ var executed = 0
+ scope.launch {
+ executed++
+ }
+ assertEquals(0, executed)
+ }
+
+ @Test
+ fun whenDispatcherResumed_doesAutoProgressCurrent() {
+ val subject = TestCoroutineDispatcher()
+ val scope = CoroutineScope(subject)
+ var executed = 0
+ scope.launch {
+ executed++
+ }
+
+ assertEquals(1, executed)
+ }
+
+ @Test
+ fun whenDispatcherResumed_doesNotAutoProgressTime() {
+ val subject = TestCoroutineDispatcher()
+ val scope = CoroutineScope(subject)
+ var executed = 0
+ scope.launch {
+ delay(1_000)
+ executed++
+ }
+
+ assertEquals(0, executed)
+ subject.advanceUntilIdle()
+ assertEquals(1, executed)
+ }
+
+ @Test
+ fun whenDispatcherPaused_thenResume_itDoesDispatchCurrent() {
+ val subject = TestCoroutineDispatcher()
+ subject.pauseDispatcher()
+ val scope = CoroutineScope(subject)
+ var executed = 0
+ scope.launch {
+ executed++
+ }
+
+ assertEquals(0, executed)
+ subject.resumeDispatcher()
+ assertEquals(1, executed)
+ }
+
+ @Test(expected = UncompletedCoroutinesError::class)
+ fun whenDispatcherHasUncompletedCoroutines_itThrowsErrorInCleanup() {
+ val subject = TestCoroutineDispatcher()
+ subject.pauseDispatcher()
+ val scope = CoroutineScope(subject)
+ scope.launch {
+ delay(1_000)
+ }
+ subject.cleanupTestCoroutines()
+ }
+
+ @Test
+ fun whenDispatchCalled_runsOnCurrentThread() {
+ val currentThread = Thread.currentThread()
+ val subject = TestCoroutineDispatcher()
+ val scope = TestCoroutineScope(subject)
+
+ val deferred = scope.async(Dispatchers.Default) {
+ withContext(subject) {
+ assertNotSame(currentThread, Thread.currentThread())
+ 3
+ }
+ }
+
+ runBlocking {
+ // just to ensure the above code terminates
+ assertEquals(3, deferred.await())
+ }
+ }
+
+ @Test
+ fun whenAllDispatchersMocked_runsOnSameThread() {
+ val currentThread = Thread.currentThread()
+ val subject = TestCoroutineDispatcher()
+ val scope = TestCoroutineScope(subject)
+
+ val deferred = scope.async(subject) {
+ withContext(subject) {
+ assertSame(currentThread, Thread.currentThread())
+ 3
+ }
+ }
+
+ runBlocking {
+ // just to ensure the above code terminates
+ assertEquals(3, deferred.await())
+ }
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt b/kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt
new file mode 100644
index 00000000..1a0833af
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import org.junit.Test
+import kotlin.test.*
+
+class TestCoroutineExceptionHandlerTest {
+ @Test
+ fun whenExceptionsCaught_avaliableViaProperty() {
+ val subject = TestCoroutineExceptionHandler()
+ val expected = IllegalArgumentException()
+ subject.handleException(subject, expected)
+ assertEquals(listOf(expected), subject.uncaughtExceptions)
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt b/kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt
new file mode 100644
index 00000000..fa14c384
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class TestCoroutineScopeTest {
+ @Test
+ fun whenGivenInvalidExceptionHandler_throwsException() {
+ val handler = CoroutineExceptionHandler { _, _ -> Unit }
+ assertFails {
+ TestCoroutineScope(handler)
+ }
+ }
+
+ @Test
+ fun whenGivenInvalidDispatcher_throwsException() {
+ assertFails {
+ TestCoroutineScope(newSingleThreadContext("incorrect call"))
+ }
+ }
+}
diff --git a/kotlinx-coroutines-test/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/test/TestDispatchersTest.kt
new file mode 100644
index 00000000..98d97053
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestDispatchersTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class TestDispatchersTest : TestBase() {
+
+ @Before
+ fun setUp() {
+ Dispatchers.resetMain()
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testSelfSet() = runTest {
+ Dispatchers.setMain(Dispatchers.Main)
+ }
+
+ @Test
+ fun testSingleThreadExecutor() = runTest {
+ val mainThread = Thread.currentThread()
+ Dispatchers.setMain(Dispatchers.Unconfined)
+ newSingleThreadContext("testSingleThread").use { threadPool ->
+ withContext(Dispatchers.Main) {
+ assertSame(mainThread, Thread.currentThread())
+ }
+
+ Dispatchers.setMain(threadPool)
+ withContext(Dispatchers.Main) {
+ assertNotSame(mainThread, Thread.currentThread())
+ }
+ assertSame(mainThread, Thread.currentThread())
+
+ withContext(Dispatchers.Main.immediate) {
+ assertNotSame(mainThread, Thread.currentThread())
+ }
+ assertSame(mainThread, Thread.currentThread())
+
+ Dispatchers.setMain(Dispatchers.Unconfined)
+ withContext(Dispatchers.Main.immediate) {
+ assertSame(mainThread, Thread.currentThread())
+ }
+ assertSame(mainThread, Thread.currentThread())
+ }
+ }
+
+ @Test
+ fun testImmediateDispatcher() = runTest {
+ Dispatchers.setMain(ImmediateDispatcher())
+ expect(1)
+ withContext(Dispatchers.Main) {
+ expect(3)
+ }
+
+ Dispatchers.setMain(RegularDispatcher())
+ withContext(Dispatchers.Main) {
+ expect(6)
+ }
+
+ finish(7)
+ }
+
+ private inner class ImmediateDispatcher : CoroutineDispatcher() {
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+ expect(2)
+ return false
+ }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) = expectUnreached()
+ }
+
+ private inner class RegularDispatcher : CoroutineDispatcher() {
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+ expect(4)
+ return true
+ }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ expect(5)
+ block.run()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-test/test/TestModuleHelpers.kt b/kotlinx-coroutines-test/test/TestModuleHelpers.kt
new file mode 100644
index 00000000..12541bd9
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestModuleHelpers.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.time.*
+
+const val SLOW = 10_000L
+
+/**
+ * Assert a block completes within a second or fail the suite
+ */
+suspend fun CoroutineScope.assertRunsFast(block: suspend CoroutineScope.() -> Unit) {
+ val start = Instant.now().toEpochMilli()
+ // don't need to be fancy with timeouts here since anything longer than a few ms is an error
+ block()
+ val duration = Instant.now().minusMillis(start).toEpochMilli()
+ Assert.assertTrue("All tests must complete within 2000ms (use longer timeouts to cause failure)", duration < 2_000)
+}
diff --git a/kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt b/kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt
new file mode 100644
index 00000000..0013a654
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import org.junit.*
+import kotlin.coroutines.*
+
+class TestRunBlockingOrderTest : TestBase() {
+ @Test
+ fun testLaunchImmediate() = runBlockingTest {
+ expect(1)
+ launch {
+ expect(2)
+ }
+ finish(3)
+ }
+
+ @Test
+ fun testYield() = runBlockingTest {
+ expect(1)
+ launch {
+ expect(2)
+ yield()
+ finish(4)
+ }
+ expect(3)
+ }
+
+ @Test
+ fun testLaunchWithDelayCompletes() = runBlockingTest {
+ expect(1)
+ launch {
+ delay(100)
+ finish(3)
+ }
+ expect(2)
+ }
+
+ @Test
+ fun testLaunchDelayOrdered() = runBlockingTest {
+ expect(1)
+ launch {
+ delay(200) // long delay
+ finish(4)
+ }
+ launch {
+ delay(100) // shorter delay
+ expect(3)
+ }
+ expect(2)
+ }
+
+ @Test
+ fun testInfiniteDelay() = runBlockingTest {
+ expect(1)
+ delay(100) // move time forward a bit some that naive time + delay gives an overflow
+ launch {
+ delay(Long.MAX_VALUE) // infinite delay
+ finish(4)
+ }
+ launch {
+ delay(100) // short delay
+ expect(3)
+ }
+ expect(2)
+ }
+
+ @Test
+ fun testAdvanceUntilIdle_inRunBlocking() = runBlockingTest {
+ expect(1)
+ assertRunsFast {
+ advanceUntilIdle() // ensure this doesn't block forever
+ }
+ finish(2)
+ }
+}
diff --git a/kotlinx-coroutines-test/test/TestRunBlockingTest.kt b/kotlinx-coroutines-test/test/TestRunBlockingTest.kt
new file mode 100644
index 00000000..e0c70915
--- /dev/null
+++ b/kotlinx-coroutines-test/test/TestRunBlockingTest.kt
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class TestRunBlockingTest {
+
+ @Test
+ fun delay_advancesTimeAutomatically() = runBlockingTest {
+ assertRunsFast {
+ delay(SLOW)
+ }
+ }
+
+ @Test
+ fun callingSuspendWithDelay_advancesAutomatically() = runBlockingTest {
+ suspend fun withDelay(): Int {
+ delay(SLOW)
+ return 3
+ }
+
+ assertRunsFast {
+ assertEquals(3, withDelay())
+ }
+ }
+
+ @Test
+ fun launch_advancesAutomatically() = runBlockingTest {
+ val job = launch {
+ delay(SLOW)
+ }
+ assertRunsFast {
+ job.join()
+ assertTrue(job.isCompleted)
+ }
+ }
+
+ @Test
+ fun async_advancesAutomatically() = runBlockingTest {
+ val deferred = async {
+ delay(SLOW)
+ 3
+ }
+
+ assertRunsFast {
+ assertEquals(3, deferred.await())
+ }
+ }
+
+ @Test
+ fun incorrectlyCalledRunblocking_doesNotHaveSameInterceptor() = runBlockingTest {
+ // this code is an error as a production test, please do not use this as an example
+
+ // this test exists to document this error condition, if it's possible to make this code work please update
+ val outerInterceptor = coroutineContext[ContinuationInterceptor]
+ // runBlocking always requires an argument to pass the context in tests
+ runBlocking {
+ assertNotSame(coroutineContext[ContinuationInterceptor], outerInterceptor)
+ }
+ }
+
+ @Test(expected = TimeoutCancellationException::class)
+ fun whenUsingTimeout_triggersWhenDelayed() = runBlockingTest {
+ assertRunsFast {
+ withTimeout(SLOW) {
+ delay(SLOW)
+ }
+ }
+ }
+
+ @Test
+ fun whenUsingTimeout_doesNotTriggerWhenFast() = runBlockingTest {
+ assertRunsFast {
+ withTimeout(SLOW) {
+ delay(0)
+ }
+ }
+ }
+
+ @Test(expected = TimeoutCancellationException::class)
+ fun whenUsingTimeout_triggersWhenWaiting() = runBlockingTest {
+ val uncompleted = CompletableDeferred<Unit>()
+ assertRunsFast {
+ withTimeout(SLOW) {
+ uncompleted.await()
+ }
+ }
+ }
+
+ @Test
+ fun whenUsingTimeout_doesNotTriggerWhenComplete() = runBlockingTest {
+ val completed = CompletableDeferred<Unit>()
+ assertRunsFast {
+ completed.complete(Unit)
+ withTimeout(SLOW) {
+ completed.await()
+ }
+ }
+ }
+
+ @Test
+ fun testDelayInAsync_withAwait() = runBlockingTest {
+ assertRunsFast {
+ val deferred = async {
+ delay(SLOW)
+ 3
+ }
+ assertEquals(3, deferred.await())
+ }
+ }
+
+ @Test(expected = TimeoutCancellationException::class)
+ fun whenUsingTimeout_inAsync_triggersWhenDelayed() = runBlockingTest {
+ val deferred = async {
+ withTimeout(SLOW) {
+ delay(SLOW)
+ }
+ }
+
+ assertRunsFast {
+ deferred.await()
+ }
+ }
+
+ @Test
+ fun whenUsingTimeout_inAsync_doesNotTriggerWhenNotDelayed() = runBlockingTest {
+ val testScope = this
+ val deferred = async {
+ withTimeout(SLOW) {
+ delay(0)
+ }
+ }
+
+ assertRunsFast {
+ deferred.await()
+ }
+ }
+
+ @Test(expected = TimeoutCancellationException::class)
+ fun whenUsingTimeout_inLaunch_triggersWhenDelayed() = runBlockingTest {
+ val job= launch {
+ withTimeout(1) {
+ delay(SLOW + 1)
+ 3
+ }
+ }
+
+ assertRunsFast {
+ job.join()
+ throw job.getCancellationException()
+ }
+ }
+
+ @Test
+ fun whenUsingTimeout_inLaunch_doesNotTriggerWhenNotDelayed() = runBlockingTest {
+ val job = launch {
+ withTimeout(SLOW) {
+ delay(0)
+ }
+ }
+
+ assertRunsFast {
+ job.join()
+ assertTrue(job.isCompleted)
+ }
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun throwingException_throws() = runBlockingTest {
+ assertRunsFast {
+ delay(SLOW)
+ throw IllegalArgumentException("Test")
+ }
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun throwingException_inLaunch_throws() = runBlockingTest {
+ val job = launch {
+ delay(SLOW)
+ throw IllegalArgumentException("Test")
+ }
+
+ assertRunsFast {
+ job.join()
+ throw job.getCancellationException().cause ?: assertFails { "expected exception" }
+ }
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun throwingException__inAsync_throws() = runBlockingTest {
+ val deferred = async {
+ delay(SLOW)
+ throw IllegalArgumentException("Test")
+ }
+
+ assertRunsFast {
+ deferred.await()
+ }
+ }
+
+ @Test
+ fun callingLaunchFunction_executesLaunchBlockImmediately() = runBlockingTest {
+ assertRunsFast {
+ var executed = false
+ launch {
+ delay(SLOW)
+ executed = true
+ }
+
+ delay(SLOW)
+ assertTrue(executed)
+ }
+ }
+
+ @Test
+ fun callingAsyncFunction_executesAsyncBlockImmediately() = runBlockingTest {
+ assertRunsFast {
+ var executed = false
+ async {
+ delay(SLOW)
+ executed = true
+ }
+ advanceTimeBy(SLOW)
+
+ assertTrue(executed)
+ }
+ }
+
+ @Test
+ fun nestingBuilders_executesSecondLevelImmediately() = runBlockingTest {
+ assertRunsFast {
+ var levels = 0
+ launch {
+ delay(SLOW)
+ levels++
+ launch {
+ delay(SLOW)
+ levels++
+ }
+ }
+ advanceUntilIdle()
+
+ assertEquals(2, levels)
+ }
+ }
+
+ @Test
+ fun testCancellationException() = runBlockingTest {
+ var actual: CancellationException? = null
+ val uncompleted = CompletableDeferred<Unit>()
+ val job = launch {
+ actual = kotlin.runCatching { uncompleted.await() }.exceptionOrNull() as? CancellationException
+ }
+
+ assertNull(actual)
+ job.cancel()
+ assertNotNull(actual)
+ }
+
+ @Test
+ fun testCancellationException_notThrown() = runBlockingTest {
+ val uncompleted = CompletableDeferred<Unit>()
+ val job = launch {
+ uncompleted.await()
+ }
+
+ job.cancel()
+ job.join()
+ }
+
+ @Test(expected = UncompletedCoroutinesError::class)
+ fun whenACoroutineLeaks_errorIsThrown() = runBlockingTest {
+ val uncompleted = CompletableDeferred<Unit>()
+ launch {
+ uncompleted.await()
+ }
+ }
+
+ @Test(expected = java.lang.IllegalArgumentException::class)
+ fun runBlockingTestBuilder_throwsOnBadDispatcher() {
+ runBlockingTest(newSingleThreadContext("name")) {
+
+ }
+ }
+
+ @Test(expected = java.lang.IllegalArgumentException::class)
+ fun runBlockingTestBuilder_throwsOnBadHandler() {
+ runBlockingTest(CoroutineExceptionHandler { _, _ -> Unit} ) {
+
+ }
+ }
+
+ @Test
+ fun pauseDispatcher_disablesAutoAdvance_forCurrent() = runBlockingTest {
+ var mutable = 0
+ pauseDispatcher {
+ launch {
+ mutable++
+ }
+ assertEquals(0, mutable)
+ runCurrent()
+ assertEquals(1, mutable)
+ }
+ }
+
+ @Test
+ fun pauseDispatcher_disablesAutoAdvance_forDelay() = runBlockingTest {
+ var mutable = 0
+ pauseDispatcher {
+ launch {
+ mutable++
+ delay(SLOW)
+ mutable++
+ }
+ assertEquals(0, mutable)
+ runCurrent()
+ assertEquals(1, mutable)
+ advanceTimeBy(SLOW)
+ assertEquals(2, mutable)
+ }
+ }
+
+ @Test
+ fun pauseDispatcher_withDelay_resumesAfterPause() = runBlockingTest {
+ var mutable = 0
+ assertRunsFast {
+ pauseDispatcher {
+ delay(1_000)
+ mutable++
+ }
+ }
+ assertEquals(1, mutable)
+ }
+
+
+ @Test(expected = IllegalAccessError::class)
+ fun testWithTestContextThrowingAnAssertionError() = runBlockingTest {
+ val expectedError = IllegalAccessError("hello")
+
+ val job = launch {
+ throw expectedError
+ }
+
+ // don't rethrow or handle the exception
+ }
+
+ @Test(expected = IllegalAccessError::class)
+ fun testExceptionHandlingWithLaunch() = runBlockingTest {
+ val expectedError = IllegalAccessError("hello")
+
+ launch {
+ throw expectedError
+ }
+ }
+
+ @Test(expected = IllegalAccessError::class)
+ fun testExceptions_notThrownImmediately() = runBlockingTest {
+ val expectedException = IllegalAccessError("hello")
+ val result = runCatching {
+ launch {
+ throw expectedException
+ }
+ }
+ runCurrent()
+ assertEquals(true, result.isSuccess)
+ }
+
+
+ private val exceptionHandler = TestCoroutineExceptionHandler()
+
+ @Test
+ fun testPartialContextOverride() = runBlockingTest(CoroutineName("named")) {
+ assertEquals(CoroutineName("named"), coroutineContext[CoroutineName])
+ assertNotNull(coroutineContext[CoroutineExceptionHandler])
+ assertNotSame(coroutineContext[CoroutineExceptionHandler], exceptionHandler)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testPartialDispatcherOverride() = runBlockingTest(Dispatchers.Unconfined) {
+ fail("Unreached")
+ }
+
+ @Test
+ fun testOverrideExceptionHandler() = runBlockingTest(exceptionHandler) {
+ assertSame(coroutineContext[CoroutineExceptionHandler], exceptionHandler)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testOverrideExceptionHandlerError() = runBlockingTest(CoroutineExceptionHandler { _, _ -> }) {
+ fail("Unreached")
+ }
+}
diff --git a/license/NOTICE.txt b/license/NOTICE.txt
new file mode 100644
index 00000000..d1d00c1a
--- /dev/null
+++ b/license/NOTICE.txt
@@ -0,0 +1,8 @@
+=========================================================================
+== NOTICE file corresponding to the section 4 d of ==
+== the Apache License, Version 2.0, ==
+== in this case for the kotlinx.coroutines library. ==
+=========================================================================
+
+kotlinx.coroutines library.
+Copyright 2016-2019 JetBrains s.r.o and respective authors and developers \ No newline at end of file
diff --git a/license/third_party/minima_LICENSE.txt b/license/third_party/minima_LICENSE.txt
new file mode 100644
index 00000000..e8c3c2d5
--- /dev/null
+++ b/license/third_party/minima_LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Parker Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/publication-validator/README.md b/publication-validator/README.md
new file mode 100644
index 00000000..7437513a
--- /dev/null
+++ b/publication-validator/README.md
@@ -0,0 +1,9 @@
+# Publication validator
+
+This is a supplementary subproject of kotlinx.coroutines to test its publication correctness.
+
+It is used as part of "Dependency validation" build chain on TeamCity:
+* kotlinx.corotoutines are built with `publishToMavenLocal`
+* kotlinx.coroutines are built with `npmPublish -PdryRun=true` to have a packed publication
+* `NpmPublicationValidator` tests that version of NPM artifact is correct and that it has neither source nor package dependencies on atomicfu
+* `MavenPublicationValidator` depends on the published artifacts and tests artifacts binary content and absence of atomicfu in the classpath
diff --git a/publication-validator/build.gradle b/publication-validator/build.gradle
new file mode 100644
index 00000000..cc19ae40
--- /dev/null
+++ b/publication-validator/build.gradle
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+plugins {
+ id 'org.jetbrains.kotlin.jvm' version '1.3.30'
+}
+
+def deployVersion = properties['DeployVersion']
+ext.coroutines_version = deployVersion
+println "Checking coroutines version $coroutines_version"
+
+group 'org.jetbrains.kotlinx'
+version '1.0-SNAPSHOT'
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.apache.commons:commons-compress:1.18'
+ testCompile 'com.google.code.gson:gson:2.8.5'
+ testCompile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+ testCompile "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+}
+
+compileKotlin {
+ kotlinOptions.jvmTarget = "1.8"
+}
+compileTestKotlin {
+ kotlinOptions.jvmTarget = "1.8"
+}
diff --git a/publication-validator/gradle.properties b/publication-validator/gradle.properties
new file mode 100644
index 00000000..29e08e8c
--- /dev/null
+++ b/publication-validator/gradle.properties
@@ -0,0 +1 @@
+kotlin.code.style=official \ No newline at end of file
diff --git a/publication-validator/gradle/wrapper/gradle-wrapper.jar b/publication-validator/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..94336fca
--- /dev/null
+++ b/publication-validator/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/publication-validator/gradle/wrapper/gradle-wrapper.properties b/publication-validator/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..ecc69863
--- /dev/null
+++ b/publication-validator/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed May 08 12:45:59 MSK 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
diff --git a/publication-validator/gradlew b/publication-validator/gradlew
new file mode 100755
index 00000000..cccdd3d5
--- /dev/null
+++ b/publication-validator/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## 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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# 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/publication-validator/gradlew.bat b/publication-validator/gradlew.bat
new file mode 100644
index 00000000..f9553162
--- /dev/null
+++ b/publication-validator/gradlew.bat
@@ -0,0 +1,84 @@
+@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 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=
+
+@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/publication-validator/settings.gradle b/publication-validator/settings.gradle
new file mode 100644
index 00000000..55203063
--- /dev/null
+++ b/publication-validator/settings.gradle
@@ -0,0 +1,8 @@
+pluginManagement {
+ repositories {
+ mavenLocal()
+ mavenCentral()
+ maven { url 'https://plugins.gradle.org/m2/' }
+ }
+}
+rootProject.name = 'kotlinx-coroutines-validator'
diff --git a/publication-validator/src/test/kotlin/kotlinx/coroutines/tools/MavenPublicationValidator.kt b/publication-validator/src/test/kotlin/kotlinx/coroutines/tools/MavenPublicationValidator.kt
new file mode 100644
index 00000000..b7f97f03
--- /dev/null
+++ b/publication-validator/src/test/kotlin/kotlinx/coroutines/tools/MavenPublicationValidator.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.validator
+
+import org.junit.*
+import org.junit.Assert.*
+import java.io.*
+import java.util.jar.*
+
+class MavenPublicationValidator {
+ private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
+
+ @Test
+ fun testNoAtomicfuInClasspath() {
+ val result = runCatching { Class.forName("kotlinx.atomicfu.AtomicInt") }
+ assertTrue(result.exceptionOrNull() is ClassNotFoundException)
+ }
+
+ @Test
+ fun testNoAtomicfuInMppJar() {
+ val clazz = Class.forName("kotlinx.coroutines.Job")
+ JarFile(clazz.protectionDomain.codeSource.location.file).checkForAtomicFu()
+ }
+
+ @Test
+ fun testNoAtomicfuInAndroidJar() {
+ val clazz = Class.forName("kotlinx.coroutines.android.HandlerDispatcher")
+ JarFile(clazz.protectionDomain.codeSource.location.file).checkForAtomicFu()
+ }
+
+ private fun JarFile.checkForAtomicFu() {
+ val foundClasses = mutableListOf<String>()
+ for (e in entries()) {
+ if (!e.name.endsWith(".class")) continue
+ val bytes = getInputStream(e).use { it.readBytes() }
+ loop@for (i in 0 until bytes.size - ATOMIC_FU_REF.size) {
+ for (j in 0 until ATOMIC_FU_REF.size) {
+ if (bytes[i + j] != ATOMIC_FU_REF[j]) continue@loop
+ }
+ foundClasses += e.name // report error at the end with all class names
+ break@loop
+ }
+ }
+ if (foundClasses.isNotEmpty()) {
+ error("Found references to atomicfu in jar file $name in the following class files: ${
+ foundClasses.joinToString("") { "\n\t\t" + it }
+ }")
+ }
+ close()
+ }
+}
diff --git a/publication-validator/src/test/kotlin/kotlinx/coroutines/tools/NpmPublicationValidator.kt b/publication-validator/src/test/kotlin/kotlinx/coroutines/tools/NpmPublicationValidator.kt
new file mode 100644
index 00000000..2c2351f7
--- /dev/null
+++ b/publication-validator/src/test/kotlin/kotlinx/coroutines/tools/NpmPublicationValidator.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.validator
+
+import com.google.gson.*
+import org.apache.commons.compress.archivers.tar.*
+import org.junit.*
+import org.junit.Assert.*
+import java.io.*
+import java.util.zip.*
+
+class NpmPublicationValidator {
+ private val VERSION = System.getenv("DeployVersion")
+ private val BUILD_DIR = System.getenv("teamcity.build.checkoutDir")
+ private val NPM_ARTIFACT = "$BUILD_DIR/kotlinx-coroutines-core/build/npm/kotlinx-coroutines-core-$VERSION.tgz"
+
+ @Test
+ fun testPackageJson() {
+ println("Checking dependencies of $NPM_ARTIFACT")
+ val visited = visit("package.json") {
+ val json = JsonParser().parse(content()).asJsonObject
+ assertEquals(VERSION, json["version"].asString)
+ assertNull(json["dependencies"])
+ val peerDependencies = json["peerDependencies"].asJsonObject
+ assertEquals(1, peerDependencies.size())
+ assertNotNull(peerDependencies["kotlin"])
+ }
+ assertEquals(1, visited)
+ }
+
+ @Test
+ fun testAtomicfuDependencies() {
+ println("Checking contents of $NPM_ARTIFACT")
+ val visited = visit(".js") {
+ val content = content()
+ assertFalse(content, content.contains("atomicfu", true))
+ assertFalse(content, content.contains("atomicint", true))
+ assertFalse(content, content.contains("atomicboolean", true))
+ }
+ assertEquals(2, visited)
+ }
+
+ private fun InputStream.content(): String {
+ val bais = ByteArrayOutputStream()
+ val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
+ var read = read(buffer, 0, DEFAULT_BUFFER_SIZE)
+ while (read >= 0) {
+ bais.write(buffer, 0, read)
+ read = read(buffer, 0, DEFAULT_BUFFER_SIZE)
+ }
+ return bais.toString()
+ }
+
+ private inline fun visit(fileSuffix: String, block: InputStream.(entry: TarArchiveEntry) -> Unit): Int {
+ var visited = 0
+ TarArchiveInputStream(GZIPInputStream(FileInputStream(NPM_ARTIFACT))).use { tais ->
+ var entry: TarArchiveEntry? = tais.nextTarEntry ?: return 0
+ do {
+ if (entry!!.name.endsWith(fileSuffix)) {
+ ++visited
+ tais.block(entry)
+ }
+ entry = tais.nextTarEntry
+ } while (entry != null)
+
+ return visited
+ }
+ }
+}
diff --git a/reactive/README.md b/reactive/README.md
new file mode 100644
index 00000000..8679a2b0
--- /dev/null
+++ b/reactive/README.md
@@ -0,0 +1,10 @@
+# Coroutines for reactive streams
+
+This directory contains modules with utilities for various reactive stream libraries.
+Module name below corresponds to the artifact name in Maven/Gradle.
+
+## Modules
+
+* [kotlinx-coroutines-reactive](kotlinx-coroutines-reactive/README.md) -- utilities for [Reactive Streams](https://www.reactive-streams.org)
+* [kotlinx-coroutines-reactor](kotlinx-coroutines-reactor/README.md) -- utilities for [Reactor](https://projectreactor.io)
+* [kotlinx-coroutines-rx2](kotlinx-coroutines-rx2/README.md) -- utilities for [RxJava 2.x](https://github.com/ReactiveX/RxJava)
diff --git a/reactive/coroutines-guide-reactive.md b/reactive/coroutines-guide-reactive.md
new file mode 100644
index 00000000..0eff27b1
--- /dev/null
+++ b/reactive/coroutines-guide-reactive.md
@@ -0,0 +1,1078 @@
+<!--- INCLUDE .*/example-reactive-([a-z]+)-([0-9]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.$$1$$2
+
+-->
+<!--- KNIT kotlinx-coroutines-rx2/test/guide/.*\.kt -->
+<!--- TEST_OUT kotlinx-coroutines-rx2/test/guide/test/GuideReactiveTest.kt
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.test
+
+import kotlinx.coroutines.guide.test.*
+import org.junit.Test
+
+class GuideReactiveTest : ReactiveTestBase() {
+-->
+
+# Guide to reactive streams with coroutines
+
+This guide explains the key differences between Kotlin coroutines and reactive streams and shows
+how they can be used together for the greater good. Prior familiarity with the basic coroutine concepts
+that are covered in [Guide to kotlinx.coroutines](../docs/coroutines-guide.md) is not required,
+but is a big plus. If you are familiar with reactive streams, you may find this guide
+a better introduction into the world of coroutines.
+
+There are several modules in `kotlinx.coroutines` project that are related to reactive streams:
+
+* [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)
+
+This guide is mostly based on [Reactive Streams](https://www.reactive-streams.org) specification and uses
+its `Publisher` interface with some examples based on [RxJava 2.x](https://github.com/ReactiveX/RxJava),
+which implements reactive streams specification.
+
+You are welcome to clone
+[`kotlinx.coroutines` project](https://github.com/Kotlin/kotlinx.coroutines)
+from GitHub to your workstation in order to
+run all the presented examples. They are contained in
+[reactive/kotlinx-coroutines-rx2/test/guide](kotlinx-coroutines-rx2/test/guide)
+directory of the project.
+
+## Table of contents
+
+<!--- TOC -->
+
+* [Differences between reactive streams and channels](#differences-between-reactive-streams-and-channels)
+ * [Basics of iteration](#basics-of-iteration)
+ * [Subscription and cancellation](#subscription-and-cancellation)
+ * [Backpressure](#backpressure)
+ * [Rx Subject vs BroadcastChannel](#rx-subject-vs-broadcastchannel)
+* [Operators](#operators)
+ * [Range](#range)
+ * [Fused filter-map hybrid](#fused-filter-map-hybrid)
+ * [Take until](#take-until)
+ * [Merge](#merge)
+* [Coroutine context](#coroutine-context)
+ * [Threads with Rx](#threads-with-rx)
+ * [Threads with coroutines](#threads-with-coroutines)
+ * [Rx observeOn](#rx-observeon)
+ * [Coroutine context to rule them all](#coroutine-context-to-rule-them-all)
+ * [Unconfined context](#unconfined-context)
+
+<!--- END_TOC -->
+
+## Differences between reactive streams and channels
+
+This section outlines key differences between reactive streams and coroutine-based channels.
+
+### Basics of iteration
+
+The [Channel] is somewhat similar concept to the following reactive stream classes:
+
+* Reactive stream [Publisher](https://github.com/reactive-streams/reactive-streams-jvm/blob/master/api/src/main/java/org/reactivestreams/Publisher.java);
+* Rx Java 1.x [Observable](https://reactivex.io/RxJava/javadoc/rx/Observable.html);
+* Rx Java 2.x [Flowable](https://reactivex.io/RxJava/2.x/javadoc/), which implements `Publisher`.
+
+They all describe an asynchronous stream of elements (aka items in Rx), either infinite or finite,
+and all of them support backpressure.
+
+However, the `Channel` always represents a _hot_ stream of items, using Rx terminology. Elements are being sent
+into the channel by producer coroutines and are received from it by consumer coroutines.
+Every [receive][ReceiveChannel.receive] invocation consumes an element from the channel.
+Let us illustrate it with the following example:
+
+<!--- INCLUDE
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ // create a channel that produces numbers from 1 to 3 with 200ms delays between them
+ val source = produce<Int> {
+ println("Begin") // mark the beginning of this coroutine in output
+ for (x in 1..3) {
+ delay(200) // wait for 200ms
+ send(x) // send number x to the channel
+ }
+ }
+ // print elements from the source
+ println("Elements:")
+ source.consumeEach { // consume elements from it
+ println(it)
+ }
+ // print elements from the source AGAIN
+ println("Again:")
+ source.consumeEach { // consume elements from it
+ println(it)
+ }
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt).
+
+This code produces the following output:
+
+```text
+Elements:
+Begin
+1
+2
+3
+Again:
+```
+
+<!--- TEST -->
+
+Notice how the "Begin" line was printed just once, because the [produce] _coroutine builder_, when it is executed,
+launches one coroutine to produce a stream of elements. All the produced elements are consumed
+with [ReceiveChannel.consumeEach][consumeEach]
+extension function. There is no way to receive the elements from this
+channel again. The channel is closed when the producer coroutine is over and an attempt to receive
+from it again cannot receive anything.
+
+Let us rewrite this code using the [publish] coroutine builder from `kotlinx-coroutines-reactive` module
+instead of [produce] from `kotlinx-coroutines-core` module. The code stays the same,
+but where `source` used to have the [ReceiveChannel] type, it now has the reactive streams'
+[Publisher](https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Publisher.html)
+type, and where [consumeEach] was used to _consume_ elements from the channel,
+now [collect][org.reactivestreams.Publisher.collect] is used to _collect_ elements from the publisher.
+
+<!--- INCLUDE
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ // create a publisher that produces numbers from 1 to 3 with 200ms delays between them
+ val source = publish<Int> {
+ // ^^^^^^^ <--- Difference from the previous examples is here
+ println("Begin") // mark the beginning of this coroutine in output
+ for (x in 1..3) {
+ delay(200) // wait for 200ms
+ send(x) // send number x to the channel
+ }
+ }
+ // print elements from the source
+ println("Elements:")
+ source.collect { // collect elements from it
+ println(it)
+ }
+ // print elements from the source AGAIN
+ println("Again:")
+ source.collect { // collect elements from it
+ println(it)
+ }
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt).
+
+Now the output of this code changes to:
+
+```text
+Elements:
+Begin
+1
+2
+3
+Again:
+Begin
+1
+2
+3
+```
+
+<!--- TEST -->
+
+This example highlights the key difference between a reactive stream and a channel. A reactive stream is a higher-order
+functional concept. While the channel _is_ a stream of elements, the reactive stream defines a recipe on how the stream of
+elements is produced. It becomes the actual stream of elements when _collected_. Each collector may receive the same or
+a different stream of elements, depending on how the corresponding implementation of `Publisher` works.
+
+The [publish] coroutine builder used in the above example does not launch a coroutine,
+but every [collect][org.reactivestreams.Publisher.collect] invocation does.
+There are two of them here and that is why we see "Begin" printed twice.
+
+In Rx lingo, this kind of publisher is called _cold_. Many standard Rx operators produce cold streams, too. We can collect
+them from a coroutine, and every collector gets the same stream of elements.
+
+> Note that we can replicate the same behaviour that we saw with channels by using Rx
+[publish](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#publish())
+operator and [connect](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/flowables/ConnectableFlowable.html#connect())
+method with it.
+
+### Subscription and cancellation
+
+In the second example from the previous section, `source.collect { ... }` was used to collect all elements.
+Instead, we can open a channel using [openSubscription][org.reactivestreams.Publisher.openSubscription]
+and iterate over it. In this way, we can have finer-grained control over our iteration
+(using `break`, for example), as shown below:
+
+<!--- INCLUDE
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.reactive.*
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ val source = Flowable.range(1, 5) // a range of five numbers
+ .doOnSubscribe { println("OnSubscribe") } // provide some insight
+ .doOnComplete { println("OnComplete") } // ...
+ .doFinally { println("Finally") } // ... into what's going on
+ var cnt = 0
+ source.openSubscription().consume { // open channel to the source
+ for (x in this) { // iterate over the channel to receive elements from it
+ println(x)
+ if (++cnt >= 3) break // break when 3 elements are printed
+ }
+ // Note: `consume` cancels the channel when this block of code is complete
+ }
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt).
+
+It produces the following output:
+
+```text
+OnSubscribe
+1
+2
+3
+Finally
+```
+
+<!--- TEST -->
+
+With an explicit `openSubscription` we should [cancel][ReceiveChannel.cancel] the corresponding
+subscription to unsubscribe from the source, but there is no need to call `cancel` explicitly --
+[consume] does that for us under the hood.
+The installed
+[doFinally](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#doFinally(io.reactivex.functions.Action))
+listener prints "Finally" to confirm that the subscription is actually being closed. Note that "OnComplete"
+is never printed because we did not consume all of the elements.
+
+We do not need to use an explicit `cancel` either if we `collect` all the elements:
+
+<!--- INCLUDE
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ val source = Flowable.range(1, 5) // a range of five numbers
+ .doOnSubscribe { println("OnSubscribe") } // provide some insight
+ .doOnComplete { println("OnComplete") } // ...
+ .doFinally { println("Finally") } // ... into what's going on
+ // collect the source fully
+ source.collect { println(it) }
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt).
+
+We get the following output:
+
+```text
+OnSubscribe
+1
+2
+3
+OnComplete
+Finally
+4
+5
+```
+
+<!--- TEST -->
+
+Notice how "OnComplete" and "Finally" are printed before the lasts elements "4" and "5".
+It happens because our `main` function in this
+example is a coroutine that we start with the [runBlocking] coroutine builder.
+Our main coroutine receives on the flowable using the `source.collect { ... }` expression.
+The main coroutine is _suspended_ while it waits for the source to emit an item.
+When the last items are emitted by `Flowable.range(1, 5)` it
+_resumes_ the main coroutine, which gets dispatched onto the main thread to print this
+ last element at a later point in time, while the source completes and prints "Finally".
+
+### Backpressure
+
+Backpressure is one of the most interesting and complex aspects of reactive streams. Coroutines can
+_suspend_ and they provide a natural answer to handling backpressure.
+
+In Rx Java 2.x, the backpressure-capable class is called
+[Flowable](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html).
+In the following example, we use [rxFlowable] coroutine builder from `kotlinx-coroutines-rx2` module to define a
+flowable that sends three integers from 1 to 3.
+It prints a message to the output before invocation of the
+suspending [send][SendChannel.send] function, so that we can study how it operates.
+
+The integers are generated in the context of the main thread, but the subscription is shifted
+to another thread using Rx
+[observeOn](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#observeOn(io.reactivex.Scheduler,%20boolean,%20int))
+operator with a buffer of size 1.
+The subscriber is slow. It takes 500 ms to process each item, which is simulated using `Thread.sleep`.
+
+<!--- INCLUDE
+import io.reactivex.schedulers.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.rx2.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ // coroutine -- fast producer of elements in the context of the main thread
+ val source = rxFlowable {
+ for (x in 1..3) {
+ send(x) // this is a suspending function
+ println("Sent $x") // print after successfully sent item
+ }
+ }
+ // subscribe on another thread with a slow subscriber using Rx
+ source
+ .observeOn(Schedulers.io(), false, 1) // specify buffer size of 1 item
+ .doOnComplete { println("Complete") }
+ .subscribe { x ->
+ Thread.sleep(500) // 500ms to process each item
+ println("Processed $x")
+ }
+ delay(2000) // suspend the main thread for a few seconds
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt).
+
+The output of this code nicely illustrates how backpressure works with coroutines:
+
+```text
+Sent 1
+Processed 1
+Sent 2
+Processed 2
+Sent 3
+Processed 3
+Complete
+```
+
+<!--- TEST -->
+
+We see here how the producer coroutine puts the first element in the buffer and is suspended while trying to send another
+one. Only after the consumer processes the first item, the producer sends the second one and resumes, etc.
+
+
+### Rx Subject vs BroadcastChannel
+
+RxJava has a concept of [Subject](https://github.com/ReactiveX/RxJava/wiki/Subject) which is an object that
+effectively broadcasts elements to all its subscribers. The matching concept in the coroutines world is called a
+[BroadcastChannel]. There is a variety of subjects in Rx with
+[BehaviorSubject](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/subjects/BehaviorSubject.html) being
+the one used to manage state:
+
+<!--- INCLUDE
+import io.reactivex.subjects.BehaviorSubject
+-->
+
+```kotlin
+fun main() {
+ val subject = BehaviorSubject.create<String>()
+ subject.onNext("one")
+ subject.onNext("two") // updates the state of BehaviorSubject, "one" value is lost
+ // now subscribe to this subject and print everything
+ subject.subscribe(System.out::println)
+ subject.onNext("three")
+ subject.onNext("four")
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt).
+
+This code prints the current state of the subject on subscription and all its further updates:
+
+
+```text
+two
+three
+four
+```
+
+<!--- TEST -->
+
+You can subscribe to subjects from a coroutine just as with any other reactive stream:
+
+<!--- INCLUDE
+import io.reactivex.subjects.BehaviorSubject
+import kotlinx.coroutines.*
+import kotlinx.coroutines.rx2.collect
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ val subject = BehaviorSubject.create<String>()
+ subject.onNext("one")
+ subject.onNext("two")
+ // now launch a coroutine to print everything
+ GlobalScope.launch(Dispatchers.Unconfined) { // launch coroutine in unconfined context
+ subject.collect { println(it) }
+ }
+ subject.onNext("three")
+ subject.onNext("four")
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt).
+
+The result is the same:
+
+```text
+two
+three
+four
+```
+
+<!--- TEST -->
+
+Here we use the [Dispatchers.Unconfined] coroutine context to launch a consuming coroutine with the same behavior as subscription in Rx.
+It basically means that the launched coroutine is going to be immediately executed in the same thread that
+is emitting elements. Contexts are covered in more details in a [separate section](#coroutine-context).
+
+The advantage of coroutines is that it is easy to get conflation behavior for single-threaded UI updates.
+A typical UI application does not need to react to every state change. Only the most recent state is relevant.
+A sequence of back-to-back updates to the application state needs to get reflected in UI only once,
+as soon as the UI thread is free. For the following example we are going to simulate this by launching
+a consuming coroutine in the context of the main thread and use the [yield] function to simulate a break in the
+sequence of updates and to release the main thread:
+
+<!--- INCLUDE
+import io.reactivex.subjects.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.rx2.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ val subject = BehaviorSubject.create<String>()
+ subject.onNext("one")
+ subject.onNext("two")
+ // now launch a coroutine to print the most recent update
+ launch { // use the context of the main thread for a coroutine
+ subject.collect { println(it) }
+ }
+ subject.onNext("three")
+ subject.onNext("four")
+ yield() // yield the main thread to the launched coroutine <--- HERE
+ subject.onComplete() // now complete the subject's sequence to cancel the consumer, too
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt).
+
+Now the coroutine processes (prints) only the most recent update:
+
+```text
+four
+```
+
+<!--- TEST -->
+
+The corresponding behavior in the pure coroutines world is implemented by [ConflatedBroadcastChannel]
+that provides the same logic on top of coroutine channels directly,
+without going through the bridge to the reactive streams:
+
+<!--- INCLUDE
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ val broadcast = ConflatedBroadcastChannel<String>()
+ broadcast.offer("one")
+ broadcast.offer("two")
+ // now launch a coroutine to print the most recent update
+ launch { // use the context of the main thread for a coroutine
+ broadcast.consumeEach { println(it) }
+ }
+ broadcast.offer("three")
+ broadcast.offer("four")
+ yield() // yield the main thread to the launched coroutine
+ broadcast.close() // now close the broadcast channel to cancel the consumer, too
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt).
+
+It produces the same output as the previous example based on `BehaviorSubject`:
+
+```text
+four
+```
+
+<!--- TEST -->
+
+Another implementation of [BroadcastChannel] is `ArrayBroadcastChannel` with an array-based buffer of
+a specified `capacity`. It can be created with `BroadcastChannel(capacity)`.
+It delivers every event to every
+subscriber as soon as their corresponding subscriptions are opened. It corresponds to
+[PublishSubject](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/subjects/PublishSubject.html) in Rx.
+The capacity of the buffer in the constructor of `ArrayBroadcastChannel` controls the numbers of elements
+that can be sent before the sender is suspended waiting for a receiver to receive those elements.
+
+## Operators
+
+Full-featured reactive stream libraries, like Rx, come with
+[a very large set of operators](https://reactivex.io/documentation/operators.html) to create, transform, combine
+and otherwise process the corresponding streams. Creating your own operators with support for
+back-pressure is [notoriously](https://akarnokd.blogspot.ru/2015/05/pitfalls-of-operator-implementations.html)
+[difficult](https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0).
+
+Coroutines and channels are designed to provide an opposite experience. There are no built-in operators,
+but processing streams of elements is extremely simple and back-pressure is supported automatically
+without you having to explicitly think about it.
+
+This section shows a coroutine-based implementation of several reactive stream operators.
+
+### Range
+
+Let's roll out own implementation of
+[range](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#range(int,%20int))
+operator for the reactive streams' `Publisher` interface. The asynchronous clean-slate implementation of this operator for
+reactive streams is explained in
+[this blog post](https://akarnokd.blogspot.ru/2017/03/java-9-flow-api-asynchronous-integer.html).
+It takes a lot of code.
+Here is the corresponding code with coroutines:
+
+<!--- INCLUDE
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.CoroutineContext
+-->
+
+```kotlin
+fun CoroutineScope.range(context: CoroutineContext, start: Int, count: Int) = publish<Int>(context) {
+ for (x in start until start + count) send(x)
+}
+```
+
+Here, `CoroutineScope` and `context` are used instead of an `Executor` and all the backpressure aspects are taken care
+of by the coroutines machinery. Note that this implementation depends only on the small reactive streams library
+that defines the `Publisher` interface and its friends.
+
+Using it from a coroutine is straightforward:
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ // Range inherits parent job from runBlocking, but overrides dispatcher with Dispatchers.Default
+ range(Dispatchers.Default, 1, 5).collect { println(it) }
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt).
+
+The result of this code is quite expected:
+
+```text
+1
+2
+3
+4
+5
+```
+
+<!--- TEST -->
+
+### Fused filter-map hybrid
+
+Reactive operators like
+[filter](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#filter(io.reactivex.functions.Predicate)) and
+[map](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#map(io.reactivex.functions.Function))
+are trivial to implement with coroutines. For a bit of challenge and showcase, let us combine them
+into the single `fusedFilterMap` operator:
+
+<!--- INCLUDE
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.reactivestreams.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun <T, R> Publisher<T>.fusedFilterMap(
+ context: CoroutineContext, // the context to execute this coroutine in
+ predicate: (T) -> Boolean, // the filter predicate
+ mapper: (T) -> R // the mapper function
+) = publish<R>(context) {
+ collect { // collect the source stream
+ if (predicate(it)) // filter part
+ send(mapper(it)) // map part
+ }
+}
+```
+
+Using `range` from the previous example we can test our `fusedFilterMap`
+by filtering for even numbers and mapping them to strings:
+
+<!--- INCLUDE
+
+fun CoroutineScope.range(start: Int, count: Int) = publish<Int> {
+ for (x in start until start + count) send(x)
+}
+-->
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ range(1, 5)
+ .fusedFilterMap(Dispatchers.Unconfined, { it % 2 == 0}, { "$it is even" })
+ .collect { println(it) } // print all the resulting strings
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt).
+
+It is not hard to see that the result is going to be:
+
+```text
+2 is even
+4 is even
+```
+
+<!--- TEST -->
+
+### Take until
+
+Let's implement our own version of
+[takeUntil](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#takeUntil(org.reactivestreams.Publisher))
+operator. It is quite [tricky](https://akarnokd.blogspot.ru/2015/05/pitfalls-of-operator-implementations.html)
+as subscriptions to two streams need to be tracked and managed.
+We need to relay all the elements from the source stream until the other stream either completes or
+emits anything. However, we have the [select] expression to rescue us in the coroutines implementation:
+
+<!--- INCLUDE
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlinx.coroutines.selects.*
+import org.reactivestreams.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun <T, U> Publisher<T>.takeUntil(context: CoroutineContext, other: Publisher<U>) = publish<T>(context) {
+ this@takeUntil.openSubscription().consume { // explicitly open channel to Publisher<T>
+ val current = this
+ other.openSubscription().consume { // explicitly open channel to Publisher<U>
+ val other = this
+ whileSelect {
+ other.onReceive { false } // bail out on any received element from `other`
+ current.onReceive { send(it); true } // resend element from this channel and continue
+ }
+ }
+ }
+}
+```
+
+This code is using [whileSelect] as a nicer shortcut to `while(select{...}) {}` loop and Kotlin's
+[consume] expressions to close the channels on exit, which unsubscribes from the corresponding publishers.
+
+The following hand-written combination of
+[range](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#range(int,%20int)) with
+[interval](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#interval(long,%20java.util.concurrent.TimeUnit,%20io.reactivex.Scheduler))
+is used for testing. It is coded using a `publish` coroutine builder
+(its pure-Rx implementation is shown in later sections):
+
+```kotlin
+fun CoroutineScope.rangeWithInterval(time: Long, start: Int, count: Int) = publish<Int> {
+ for (x in start until start + count) {
+ delay(time) // wait before sending each number
+ send(x)
+ }
+}
+```
+
+The following code shows how `takeUntil` works:
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ val slowNums = rangeWithInterval(200, 1, 10) // numbers with 200ms interval
+ val stop = rangeWithInterval(500, 1, 10) // the first one after 500ms
+ slowNums.takeUntil(Dispatchers.Unconfined, stop).collect { println(it) } // let's test it
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt).
+
+Producing
+
+```text
+1
+2
+```
+
+<!--- TEST -->
+
+### Merge
+
+There are always at least two ways for processing multiple streams of data with coroutines. One way involving
+[select] was shown in the previous example. The other way is just to launch multiple coroutines. Let
+us implement
+[merge](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#merge(org.reactivestreams.Publisher))
+operator using the latter approach:
+
+<!--- INCLUDE
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.reactivestreams.*
+import kotlin.coroutines.*
+-->
+
+```kotlin
+fun <T> Publisher<Publisher<T>>.merge(context: CoroutineContext) = publish<T>(context) {
+ collect { pub -> // for each publisher collected
+ launch { // launch a child coroutine
+ pub.collect { send(it) } // resend all element from this publisher
+ }
+ }
+}
+```
+
+Notice that all the coroutines that are
+being launched here are the children of the `publish`
+coroutine and will get cancelled when the `publish` coroutine is cancelled or is otherwise completed.
+Moreover, since the parent coroutine waits until all the children are complete, this implementation fully
+merges all the received streams.
+
+For a test, let us start with the `rangeWithInterval` function from the previous example and write a
+producer that sends its results twice with some delay:
+
+<!--- INCLUDE
+
+fun CoroutineScope.rangeWithInterval(time: Long, start: Int, count: Int) = publish<Int> {
+ for (x in start until start + count) {
+ delay(time) // wait before sending each number
+ send(x)
+ }
+}
+-->
+
+```kotlin
+fun CoroutineScope.testPub() = publish<Publisher<Int>> {
+ send(rangeWithInterval(250, 1, 4)) // number 1 at 250ms, 2 at 500ms, 3 at 750ms, 4 at 1000ms
+ delay(100) // wait for 100 ms
+ send(rangeWithInterval(500, 11, 3)) // number 11 at 600ms, 12 at 1100ms, 13 at 1600ms
+ delay(1100) // wait for 1.1s - done in 1.2 sec after start
+}
+```
+
+The test code is to use `merge` on `testPub` and to display the results:
+
+```kotlin
+fun main() = runBlocking<Unit> {
+ testPub().merge(Dispatchers.Unconfined).collect { println(it) } // print the whole stream
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt).
+
+And the results should be:
+
+```text
+1
+2
+11
+3
+4
+12
+13
+```
+
+<!--- TEST -->
+
+## Coroutine context
+
+All the example operators that are shown in the previous section have an explicit
+[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/)
+parameter. In the Rx world it roughly corresponds to
+a [Scheduler](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Scheduler.html).
+
+### Threads with Rx
+
+The following example shows the basics of threading context management with Rx.
+Here `rangeWithIntervalRx` is an implementation of `rangeWithInterval` function using Rx
+`zip`, `range`, and `interval` operators.
+
+<!--- INCLUDE
+import io.reactivex.*
+import io.reactivex.functions.BiFunction
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+-->
+
+```kotlin
+fun rangeWithIntervalRx(scheduler: Scheduler, time: Long, start: Int, count: Int): Flowable<Int> =
+ Flowable.zip(
+ Flowable.range(start, count),
+ Flowable.interval(time, TimeUnit.MILLISECONDS, scheduler),
+ BiFunction { x, _ -> x })
+
+fun main() {
+ rangeWithIntervalRx(Schedulers.computation(), 100, 1, 3)
+ .subscribe { println("$it on thread ${Thread.currentThread().name}") }
+ Thread.sleep(1000)
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt).
+
+We are explicitly passing the
+[Schedulers.computation()](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/Schedulers.html#computation())
+scheduler to our `rangeWithIntervalRx` operator and
+it is going to be executed in Rx computation thread pool. The output is going to be similar to the following one:
+
+```text
+1 on thread RxComputationThreadPool-1
+2 on thread RxComputationThreadPool-1
+3 on thread RxComputationThreadPool-1
+```
+
+<!--- TEST FLEXIBLE_THREAD -->
+
+### Threads with coroutines
+
+In the world of coroutines `Schedulers.computation()` roughly corresponds to [Dispatchers.Default],
+so the previous example is similar to the following one:
+
+<!--- INCLUDE
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.CoroutineContext
+-->
+
+```kotlin
+fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
+ for (x in start until start + count) {
+ delay(time) // wait before sending each number
+ send(x)
+ }
+}
+
+fun main() {
+ Flowable.fromPublisher(rangeWithInterval(Dispatchers.Default, 100, 1, 3))
+ .subscribe { println("$it on thread ${Thread.currentThread().name}") }
+ Thread.sleep(1000)
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt).
+
+The produced output is going to be similar to:
+
+```text
+1 on thread ForkJoinPool.commonPool-worker-1
+2 on thread ForkJoinPool.commonPool-worker-1
+3 on thread ForkJoinPool.commonPool-worker-1
+```
+
+<!--- TEST LINES_START -->
+
+Here we've used Rx
+[subscribe](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#subscribe(io.reactivex.functions.Consumer))
+operator that does not have its own scheduler and operates on the same thread that the publisher -- on a default
+shared pool of threads in this example.
+
+### Rx observeOn
+
+In Rx you use special operators to modify the threading context for operations in the chain. You
+can find some [good guides](https://tomstechnicalblog.blogspot.ru/2016/02/rxjava-understanding-observeon-and.html)
+about them, if you are not familiar.
+
+For example, there is
+[observeOn](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#observeOn(io.reactivex.Scheduler))
+operator. Let us modify the previous example to observe using `Schedulers.computation()`:
+
+<!--- INCLUDE
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import io.reactivex.schedulers.Schedulers
+import kotlin.coroutines.CoroutineContext
+-->
+
+```kotlin
+fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
+ for (x in start until start + count) {
+ delay(time) // wait before sending each number
+ send(x)
+ }
+}
+
+fun main() {
+ Flowable.fromPublisher(rangeWithInterval(Dispatchers.Default, 100, 1, 3))
+ .observeOn(Schedulers.computation()) // <-- THIS LINE IS ADDED
+ .subscribe { println("$it on thread ${Thread.currentThread().name}") }
+ Thread.sleep(1000)
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt).
+
+Here is the difference in output, notice "RxComputationThreadPool":
+
+```text
+1 on thread RxComputationThreadPool-1
+2 on thread RxComputationThreadPool-1
+3 on thread RxComputationThreadPool-1
+```
+
+<!--- TEST FLEXIBLE_THREAD -->
+
+### Coroutine context to rule them all
+
+A coroutine is always working in some context. For example, let us start a coroutine
+in the main thread with [runBlocking] and iterate over the result of the Rx version of `rangeWithIntervalRx` operator,
+instead of using Rx `subscribe` operator:
+
+<!--- INCLUDE
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import io.reactivex.functions.BiFunction
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+-->
+
+```kotlin
+fun rangeWithIntervalRx(scheduler: Scheduler, time: Long, start: Int, count: Int): Flowable<Int> =
+ Flowable.zip(
+ Flowable.range(start, count),
+ Flowable.interval(time, TimeUnit.MILLISECONDS, scheduler),
+ BiFunction { x, _ -> x })
+
+fun main() = runBlocking<Unit> {
+ rangeWithIntervalRx(Schedulers.computation(), 100, 1, 3)
+ .collect { println("$it on thread ${Thread.currentThread().name}") }
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt).
+
+The resulting messages are going to be printed in the main thread:
+
+```text
+1 on thread main
+2 on thread main
+3 on thread main
+```
+
+<!--- TEST LINES_START -->
+
+### Unconfined context
+
+Most Rx operators do not have any specific thread (scheduler) associated with them and are working
+in whatever thread they happen to be invoked. We've seen it in the example with the `subscribe` operator
+in the [threads with Rx](#threads-with-rx) section.
+
+In the world of coroutines, [Dispatchers.Unconfined] context serves a similar role. Let us modify our previous example,
+but instead of iterating over the source `Flowable` from the `runBlocking` coroutine that is confined
+to the main thread, we launch a new coroutine in the `Dispatchers.Unconfined` context, while the main coroutine
+simply waits for its completion using [Job.join]:
+
+<!--- INCLUDE
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import io.reactivex.functions.BiFunction
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+-->
+
+```kotlin
+fun rangeWithIntervalRx(scheduler: Scheduler, time: Long, start: Int, count: Int): Flowable<Int> =
+ Flowable.zip(
+ Flowable.range(start, count),
+ Flowable.interval(time, TimeUnit.MILLISECONDS, scheduler),
+ BiFunction { x, _ -> x })
+
+fun main() = runBlocking<Unit> {
+ val job = launch(Dispatchers.Unconfined) { // launch a new coroutine in Unconfined context (without its own thread pool)
+ rangeWithIntervalRx(Schedulers.computation(), 100, 1, 3)
+ .collect { println("$it on thread ${Thread.currentThread().name}") }
+ }
+ job.join() // wait for our coroutine to complete
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt).
+
+Now, the output shows that the code of the coroutine is executing in the Rx computation thread pool, just
+like our initial example using the Rx `subscribe` operator.
+
+```text
+1 on thread RxComputationThreadPool-1
+2 on thread RxComputationThreadPool-1
+3 on thread RxComputationThreadPool-1
+```
+
+<!--- TEST LINES_START -->
+
+Note that the [Dispatchers.Unconfined] context should be used with care. It may improve the overall performance on certain tests,
+due to the increased stack-locality of operations and less scheduling overhead, but it also produces deeper stacks
+and makes it harder to reason about asynchronicity of the code that is using it.
+
+If a coroutine sends an element to a channel, then the thread that invoked the
+[send][SendChannel.send] may start executing the code of the coroutine with the [Dispatchers.Unconfined] dispatcher.
+The original producer coroutine that invoked `send` is paused until the unconfined consumer coroutine hits its next
+suspension point. This is very similar to a lock-step single-threaded `onNext` execution in the Rx world in the absense
+of thread-shifting operators. It is a normal default for Rx, because operators are usually doing very small chunks
+of work and you have to combine many operators for a complex processing. However, this is unusual with coroutines,
+where you can have an arbitrary complex processing in a coroutine. Usually, you only need to chain stream-processing
+coroutines for complex pipelines with fan-in and fan-out between multiple worker coroutines.
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
+[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
+[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+<!--- INDEX kotlinx.coroutines.channels -->
+[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.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
+[ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
+[ReceiveChannel.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/cancel.html
+[consume]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/consume.html
+[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
+[BroadcastChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-broadcast-channel/index.html
+[ConflatedBroadcastChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-conflated-broadcast-channel/index.html
+<!--- INDEX kotlinx.coroutines.selects -->
+[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
+[whileSelect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/while-select.html
+<!--- MODULE kotlinx-coroutines-reactive -->
+<!--- INDEX kotlinx.coroutines.reactive -->
+[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
+[org.reactivestreams.Publisher.collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/collect.html
+[org.reactivestreams.Publisher.openSubscription]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/open-subscription.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
+<!--- END -->
+
+
diff --git a/reactive/kotlinx-coroutines-reactive/README.md b/reactive/kotlinx-coroutines-reactive/README.md
new file mode 100644
index 00000000..69691e8e
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/README.md
@@ -0,0 +1,48 @@
+# Module kotlinx-coroutines-reactive
+
+Utilities for [Reactive Streams](https://www.reactive-streams.org).
+
+Coroutine builders:
+
+| **Name** | **Result** | **Scope** | **Description**
+| --------------- | ----------------------------- | ---------------- | ---------------
+| [publish] | `Publisher` | [ProducerScope] | Cold reactive publisher that starts coroutine on subscribe
+
+Suspending extension functions and suspending iteration:
+
+| **Name** | **Description**
+| -------- | ---------------
+| [Publisher.awaitFirst][org.reactivestreams.Publisher.awaitFirst] | Returns the first value from the given publisher
+| [Publisher.awaitFirstOrDefault][org.reactivestreams.Publisher.awaitFirstOrDefault] | Returns the first value from the given publisher or default
+| [Publisher.awaitFirstOrElse][org.reactivestreams.Publisher.awaitFirstOrElse] | Returns the first value from the given publisher or default from a function
+| [Publisher.awaitFirstOrNull][org.reactivestreams.Publisher.awaitFirstOrNull] | Returns the first value from the given publisher or null
+| [Publisher.awaitLast][org.reactivestreams.Publisher.awaitFirst] | Returns the last value from the given publisher
+| [Publisher.awaitSingle][org.reactivestreams.Publisher.awaitSingle] | Returns the single value from the given publisher
+| [Publisher.openSubscription][org.reactivestreams.Publisher.openSubscription] | Subscribes to publisher and returns [ReceiveChannel]
+
+Conversion functions:
+
+| **Name** | **Description**
+| -------- | ---------------
+| [ReceiveChannel.asPublisher][kotlinx.coroutines.channels.ReceiveChannel.asPublisher] | Converts streaming channel to hot publisher
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+<!--- 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
+<!--- MODULE kotlinx-coroutines-reactive -->
+<!--- INDEX kotlinx.coroutines.reactive -->
+[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.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
+[org.reactivestreams.Publisher.openSubscription]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/open-subscription.html
+[kotlinx.coroutines.channels.ReceiveChannel.asPublisher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/kotlinx.coroutines.channels.-receive-channel/as-publisher.html
+<!--- END -->
+
+# Package kotlinx.coroutines.reactive
+
+Utilities for [Reactive Streams](https://www.reactive-streams.org).
diff --git a/reactive/kotlinx-coroutines-reactive/build.gradle b/reactive/kotlinx-coroutines-reactive/build.gradle
new file mode 100644
index 00000000..f544ab44
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+dependencies {
+ compile "org.reactivestreams:reactive-streams:$reactive_streams_version"
+ testCompile "org.reactivestreams:reactive-streams-tck:$reactive_streams_version"
+}
+
+task testNG(type: Test) {
+ useTestNG()
+ reports.html.destination = file("$buildDir/reports/testng")
+ include '**/*ReactiveStreamTckTest.*'
+ // Skip testNG when tests are filtered with --tests, otherwise it simply fails
+ onlyIf {
+ filter.includePatterns.isEmpty()
+ }
+ doFirst {
+ // Classic gradle, nothing works without doFirst
+ println "TestNG tests: ($includes)"
+ }
+}
+
+test {
+ dependsOn(testNG)
+ reports.html.destination = file("$buildDir/reports/junit")
+}
+
+tasks.withType(dokka.getClass()) {
+ externalDocumentationLink {
+ url = new URL("https://www.reactive-streams.org/reactive-streams-$reactive_streams_version-javadoc/")
+ packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/package.list b/reactive/kotlinx-coroutines-reactive/package.list
new file mode 100644
index 00000000..6a8ba62f
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/package.list
@@ -0,0 +1 @@
+org.reactivestreams
diff --git a/reactive/kotlinx-coroutines-reactive/src/Await.kt b/reactive/kotlinx-coroutines-reactive/src/Await.kt
new file mode 100644
index 00000000..072773a4
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/src/Await.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016-2018 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 org.reactivestreams.Publisher
+import org.reactivestreams.Subscriber
+import org.reactivestreams.Subscription
+import java.util.*
+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.
+ *
+ * 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].
+ *
+ * @throws NoSuchElementException if 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.
+ *
+ * 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].
+ */
+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.
+ *
+ * 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].
+ */
+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.
+ *
+ * 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].
+ */
+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.
+ *
+ * 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].
+ *
+ * @throws NoSuchElementException if 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.
+ *
+ * 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].
+ *
+ * @throws NoSuchElementException if publisher does not emit any value
+ * @throws IllegalArgumentException if publisher emits more than one value
+ */
+public suspend fun <T> Publisher<T>.awaitSingle(): T = awaitOne(Mode.SINGLE)
+
+// ------------------------ private ------------------------
+
+// ContextInjector service is implemented in `kotlinx-coroutines-reactor` module only.
+// If `kotlinx-coroutines-reactor` module is not included, the list is empty.
+private val contextInjectors: Array<ContextInjector> =
+ ServiceLoader.load(ContextInjector::class.java, ContextInjector::class.java.classLoader).iterator().asSequence().toList().toTypedArray() // R8 opto
+
+private fun <T> Publisher<T>.injectCoroutineContext(coroutineContext: CoroutineContext) =
+ contextInjectors.fold(this) { pub, contextInjector ->
+ contextInjector.injectCoroutineContext(pub, coroutineContext)
+ }
+
+private enum class Mode(val s: String) {
+ FIRST("awaitFirst"),
+ FIRST_OR_DEFAULT("awaitFirstOrDefault"),
+ LAST("awaitLast"),
+ SINGLE("awaitSingle");
+ override fun toString(): String = s
+}
+
+private suspend fun <T> Publisher<T>.awaitOne(
+ mode: Mode,
+ default: T? = null
+): T = suspendCancellableCoroutine { cont ->
+ injectCoroutineContext(cont.context).subscribe(object : Subscriber<T> {
+ private lateinit var subscription: Subscription
+ private var value: T? = null
+ private var seenValue = false
+
+ override fun onSubscribe(sub: Subscription) {
+ subscription = sub
+ cont.invokeOnCancellation { sub.cancel() }
+ sub.request(if (mode == Mode.FIRST) 1 else Long.MAX_VALUE)
+ }
+
+ override fun onNext(t: T) {
+ when (mode) {
+ Mode.FIRST, Mode.FIRST_OR_DEFAULT -> {
+ if (!seenValue) {
+ seenValue = true
+ subscription.cancel()
+ cont.resume(t)
+ }
+ }
+ Mode.LAST, Mode.SINGLE -> {
+ if (mode == Mode.SINGLE && seenValue) {
+ subscription.cancel()
+ if (cont.isActive)
+ cont.resumeWithException(IllegalArgumentException("More than one onNext value for $mode"))
+ } else {
+ value = t
+ seenValue = true
+ }
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun onComplete() {
+ if (seenValue) {
+ if (cont.isActive) cont.resume(value as T)
+ return
+ }
+ when {
+ mode == Mode.FIRST_OR_DEFAULT -> {
+ cont.resume(default as T)
+ }
+ cont.isActive -> {
+ cont.resumeWithException(NoSuchElementException("No value received via onNext for $mode"))
+ }
+ }
+ }
+
+ override fun onError(e: Throwable) {
+ cont.resumeWithException(e)
+ }
+ })
+}
+
diff --git a/reactive/kotlinx-coroutines-reactive/src/Channel.kt b/reactive/kotlinx-coroutines-reactive/src/Channel.kt
new file mode 100644
index 00000000..6cf11b7a
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/src/Channel.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactive
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+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.
+ *
+ * **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).
+ *
+ * @param request how many items to request from publisher in advance (optional, one by default).
+ */
+@ObsoleteCoroutinesApi
+@Suppress("CONFLICTING_OVERLOADS")
+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.WARNING, replaceWith = ReplaceWith("this.collect(action)"))
+public suspend inline fun <T> Publisher<T>.consumeEach(action: (T) -> 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.
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public suspend inline fun <T> Publisher<T>.collect(action: (T) -> Unit) =
+ openSubscription().consumeEach(action)
+
+@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "SubscriberImplementation")
+private class SubscriptionChannel<T>(
+ private val request: Int
+) : LinkedListChannel<T>(), Subscriber<T> {
+ init {
+ require(request >= 0) { "Invalid request size: $request" }
+ }
+
+ private val _subscription = atomic<Subscription?>(null)
+
+ // requested from subscription minus number of received minus number of enqueued receivers,
+ // can be negative if we have receivers, but no subscription yet
+ private val _requested = atomic(0)
+
+ // AbstractChannel overrides
+ @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER")
+ override fun onReceiveEnqueued() {
+ _requested.loop { wasRequested ->
+ val subscription = _subscription.value
+ val needRequested = wasRequested - 1
+ if (subscription != null && needRequested < 0) { // need to request more from subscription
+ // try to fixup by making request
+ if (wasRequested != request && !_requested.compareAndSet(wasRequested, request))
+ return@loop // continue looping if failed
+ subscription.request((request - needRequested).toLong())
+ return
+ }
+ // just do book-keeping
+ if (_requested.compareAndSet(wasRequested, needRequested)) return
+ }
+ }
+
+ @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER")
+ override fun onReceiveDequeued() {
+ _requested.incrementAndGet()
+ }
+
+ @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER")
+ override fun onClosedIdempotent(closed: LockFreeLinkedListNode) {
+ _subscription.getAndSet(null)?.cancel() // cancel exactly once
+ }
+
+ // Subscriber overrides
+ override fun onSubscribe(s: Subscription) {
+ _subscription.value = s
+ while (true) { // lock-free loop on _requested
+ if (isClosedForSend) {
+ s.cancel()
+ return
+ }
+ val wasRequested = _requested.value
+ if (wasRequested >= request) return // ok -- normal story
+ // otherwise, receivers came before we had subscription or need to make initial request
+ // try to fixup by making request
+ if (!_requested.compareAndSet(wasRequested, request)) continue
+ s.request((request - wasRequested).toLong())
+ return
+ }
+ }
+
+ override fun onNext(t: T) {
+ _requested.decrementAndGet()
+ offer(t)
+ }
+
+ override fun onComplete() {
+ close(cause = null)
+ }
+
+ override fun onError(e: Throwable) {
+ close(cause = e)
+ }
+}
+
diff --git a/reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt b/reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt
new file mode 100644
index 00000000..45f65530
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt
@@ -0,0 +1,15 @@
+package kotlinx.coroutines.reactive
+
+import kotlinx.coroutines.InternalCoroutinesApi
+import org.reactivestreams.Publisher
+import kotlin.coroutines.CoroutineContext
+
+/** @suppress */
+@InternalCoroutinesApi
+public interface ContextInjector {
+ /**
+ * Injects `ReactorContext` element from the given context into the `SubscriberContext` of the publisher.
+ * This API used as an indirection layer between `reactive` and `reactor` modules.
+ */
+ public fun <T> injectCoroutineContext(publisher: Publisher<T>, coroutineContext: CoroutineContext): Publisher<T>
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/src/Convert.kt b/reactive/kotlinx-coroutines-reactive/src/Convert.kt
new file mode 100644
index 00000000..a7ae128e
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/src/Convert.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2018 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 org.reactivestreams.*
+import kotlin.coroutines.*
+
+/**
+ * Converts a stream of elements received from the channel to the hot reactive publisher.
+ *
+ * Every subscriber receives values from this channel in **fan-out** fashion. If the are multiple subscribers,
+ * they'll receive values in round-robin way.
+ *
+ * **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).
+ *
+ * @param context -- the coroutine context from which the resulting observable is going to be signalled
+ */
+@ObsoleteCoroutinesApi
+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
new file mode 100644
index 00000000..40dfec03
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/src/Migration.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.reactive
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.reactivestreams.*
+
+// Binary compatibility with Spring 5.2 RC
+@Deprecated(
+ message = "Replaced in favor of ReactiveFlow extension, please import kotlinx.coroutines.reactive.* instead of kotlinx.coroutines.reactive.FlowKt",
+ level = DeprecationLevel.ERROR
+)
+@JvmName("asFlow")
+public fun <T : Any> Publisher<T>.asFlowDeprecated(): Flow<T> = asFlow()
+
+// Binary compatibility with Spring 5.2 RC
+@Deprecated(
+ message = "Replaced in favor of ReactiveFlow extension, please import kotlinx.coroutines.reactive.* instead of kotlinx.coroutines.reactive.FlowKt",
+ level = DeprecationLevel.ERROR
+)
+@JvmName("asPublisher")
+public fun <T : Any> Flow<T>.asPublisherDeprecated(): Publisher<T> = asPublisher()
+
+@FlowPreview
+@Deprecated(
+ message = "batchSize parameter is deprecated, use .buffer() instead to control the backpressure",
+ level = DeprecationLevel.ERROR,
+ replaceWith = ReplaceWith("asFlow().buffer(batchSize)", imports = ["kotlinx.coroutines.flow.*"])
+)
+public fun <T : Any> Publisher<T>.asFlow(batchSize: Int): Flow<T> = asFlow().buffer(batchSize) \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/src/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/Publish.kt
new file mode 100644
index 00000000..a7d53876
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/src/Publish.kt
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2016-2019 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.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+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.
+ * Every time the returned publisher is subscribed, it starts a new coroutine.
+ * Coroutine emits items with `send`. Unsubscribing cancels running coroutine.
+ *
+ * Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
+ * `onNext` is not invoked concurrently.
+ *
+ * | **Coroutine action** | **Signal to subscriber**
+ * | -------------------------------------------- | ------------------------
+ * | `send` | `onNext`
+ * | Normal completion or `close` without cause | `onComplete`
+ * | Failure with exception or `close` with cause | `onError`
+ *
+ * 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.
+ *
+ * **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.
+ */
+@ExperimentalCoroutinesApi
+public fun <T> publish(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Publisher<T> {
+ require(context[Job] === null) { "Publisher context cannot contain job in it." +
+ "Its lifecycle should be managed via subscription. Had $context" }
+ return publishInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.publish is deprecated in favour of top-level publish",
+ level = DeprecationLevel.WARNING,
+ 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, block)
+
+/** @suppress For internal use from other reactive integration modules only */
+@InternalCoroutinesApi
+public fun <T> publishInternal(
+ scope: CoroutineScope, // support for legacy publish in scope
+ context: CoroutineContext,
+ block: suspend ProducerScope<T>.() -> Unit
+): Publisher<T> = Publisher { subscriber ->
+ // specification requires NPE on null subscriber
+ if (subscriber == null) throw NullPointerException("Subscriber cannot be null")
+ val newContext = scope.newCoroutineContext(context)
+ val coroutine = PublisherCoroutine(newContext, subscriber)
+ subscriber.onSubscribe(coroutine) // do it first (before starting coroutine), to avoid unnecessary suspensions
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+}
+
+private const val CLOSED = -1L // closed, but have not signalled onCompleted/onError yet
+private const val SIGNALLED = -2L // already signalled subscriber onCompleted/onError
+
+@Suppress("CONFLICTING_JVM_DECLARATIONS", "RETURN_TYPE_MISMATCH_ON_INHERITANCE")
+@InternalCoroutinesApi
+public class PublisherCoroutine<in T>(
+ parentContext: CoroutineContext,
+ private val subscriber: Subscriber<T>
+) : AbstractCoroutine<Unit>(parentContext, 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
+ private val mutex = Mutex(locked = true)
+
+ private val _nRequested = atomic(0L) // < 0 when closed (CLOSED or SIGNALLED)
+
+ @Volatile
+ private var cancelled = false // true when Subscription.cancel() is invoked
+
+ override val isClosedForSend: Boolean get() = isCompleted
+ override val isFull: Boolean = mutex.isLocked
+ override fun close(cause: Throwable?): Boolean = cancelCoroutine(cause)
+ override fun invokeOnClose(handler: (Throwable?) -> Unit) =
+ throw UnsupportedOperationException("PublisherCoroutine doesn't support invokeOnClose")
+
+ override fun offer(element: T): Boolean {
+ if (!mutex.tryLock()) return false
+ doLockedNext(element)
+ return true
+ }
+
+ 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)
+ }
+
+ override val onSend: SelectClause2<T, SendChannel<T>>
+ get() = this
+
+ // registerSelectSend
+ @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)
+ block(this)
+ }
+ }
+
+ /*
+ * This code is not trivial because of the two 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.
+ * 2. Normally, `onComplete/onError` notification is sent only when coroutine and all its children are complete.
+ * However, nothing prevents `publish` coroutine from leaking reference to it send channel to some
+ * 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.
+ */
+
+ // 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
+ if (!isActive) {
+ unlockAndCheckCompleted()
+ throw 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)
+ unlockAndCheckCompleted()
+ throw e
+ }
+ // now update nRequested
+ while (true) { // lock-free loop on nRequested
+ val current = _nRequested.value
+ if (current < 0) break // closed from inside onNext => unlock
+ if (current == Long.MAX_VALUE) break // no back-pressure => unlock
+ val updated = current - 1
+ if (_nRequested.compareAndSet(current, updated)) {
+ if (updated == 0L) {
+ // return to keep locked due to back-pressure
+ return
+ }
+ break // unlock if updated > 0
+ }
+ }
+ unlockAndCheckCompleted()
+ }
+
+ private fun unlockAndCheckCompleted() {
+ /*
+ * There is no sense to check completion before doing `unlock`, because completion might
+ * happen after this check and before `unlock` (see `signalCompleted` that does not do anything
+ * if it fails to acquire the lock that we are still holding).
+ * We have to recheck `isCompleted` after `unlock` anyway.
+ */
+ mutex.unlock()
+ // check isCompleted and and try to regain lock to signal completion
+ if (isCompleted && mutex.tryLock()) {
+ doLockedSignalCompleted(completionCause, completionCauseHandled)
+ }
+ }
+
+ // 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) handleCoroutineException(context, cause)
+ return
+ }
+
+ 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()) {
+ handleCoroutineException(context, cause)
+ }
+ } else {
+ subscriber.onComplete()
+ }
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
+ }
+ } finally {
+ mutex.unlock()
+ }
+ }
+
+ override fun request(n: Long) {
+ if (n <= 0) {
+ // Specification requires 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
+ var upd = cur + n
+ if (upd < 0 || n == Long.MAX_VALUE)
+ upd = Long.MAX_VALUE
+ if (cur == upd) return // nothing to do
+ if (_nRequested.compareAndSet(cur, upd)) {
+ // unlock the mutex when we don't have back-pressure anymore
+ if (cur == 0L) {
+ unlockAndCheckCompleted()
+ }
+ return
+ }
+ }
+ }
+
+ // assert: isCompleted
+ private fun signalCompleted(cause: Throwable?, handled: Boolean) {
+ while (true) { // lock-free loop for nRequested
+ val current = _nRequested.value
+ if (current == SIGNALLED) return // some other thread holding lock already signalled cancellation/completion
+ check(current >= 0) // no other thread could have marked it as CLOSED, because onCompleted[Exceptionally] is invoked once
+ if (!_nRequested.compareAndSet(current, CLOSED)) continue // retry on failed CAS
+ // Ok -- marked as CLOSED, now can unlock the mutex if it was locked due to backpressure
+ if (current == 0L) {
+ doLockedSignalCompleted(cause, handled)
+ } else {
+ // otherwise mutex was either not locked or locked in concurrent onNext... try lock it to signal completion
+ if (mutex.tryLock()) doLockedSignalCompleted(cause, handled)
+ // Note: if failed `tryLock`, then `doLockedNext` will signal after performing `unlock`
+ }
+ return // done anyway
+ }
+ }
+
+ override fun onCompleted(value: Unit) {
+ signalCompleted(null, false)
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ signalCompleted(cause, handled)
+ }
+
+ override fun cancel() {
+ // Specification requires that after cancellation publisher stops signalling
+ // This flag distinguishes subscription cancellation request from the job crash
+ cancelled = true
+ super.cancel(null)
+ }
+
+ private fun Throwable.isFatal() = this is VirtualMachineError || this is ThreadDeath || this is LinkageError
+}
diff --git a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt
new file mode 100644
index 00000000..559068f5
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactive
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.intrinsics.*
+import org.reactivestreams.*
+import java.util.*
+import kotlin.coroutines.*
+
+/**
+ * Transforms the given reactive [Publisher] into [Flow].
+ * Use [buffer] operator on the resulting flow to specify the size of the backpressure.
+ * More precisely, to it specifies the value of the subscription's [request][Subscription.request].
+ * `1` is used by default.
+ *
+ * If any of the resulting flow transformations fails, subscription is immediately cancelled and all in-flights elements
+ * are discarded.
+ */
+@ExperimentalCoroutinesApi
+public fun <T : Any> Publisher<T>.asFlow(): Flow<T> =
+ PublisherAsFlow(this, 1)
+
+/**
+ * Transforms the given flow to a spec-compliant [Publisher].
+ */
+@ExperimentalCoroutinesApi
+public fun <T : Any> Flow<T>.asPublisher(): Publisher<T> = FlowAsPublisher(this)
+
+private class PublisherAsFlow<T : Any>(
+ private val publisher: Publisher<T>,
+ capacity: Int
+) : ChannelFlow<T>(EmptyCoroutineContext, capacity) {
+ override fun create(context: CoroutineContext, capacity: Int): ChannelFlow<T> =
+ PublisherAsFlow(publisher, capacity)
+
+ override fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> {
+ // use another channel for conflation (cannot do openSubscription)
+ if (capacity < 0) return super.produceImpl(scope)
+ // Open subscription channel directly
+ val channel = publisher
+ .injectCoroutineContext(scope.coroutineContext)
+ .openSubscription(capacity)
+ val handle = scope.coroutineContext[Job]?.invokeOnCompletion(onCancelling = true) { cause ->
+ channel.cancel(cause?.let {
+ it as? CancellationException ?: CancellationException("Job was cancelled", it)
+ })
+ }
+ if (handle != null && handle !== NonDisposableHandle) {
+ (channel as SendChannel<*>).invokeOnClose {
+ handle.dispose()
+ }
+ }
+ return channel
+ }
+
+ private val requestSize: Long
+ get() = when (capacity) {
+ Channel.CONFLATED -> Long.MAX_VALUE // request all and conflate incoming
+ Channel.RENDEZVOUS -> 1L // need to request at least one anyway
+ Channel.UNLIMITED -> Long.MAX_VALUE // reactive streams way to say "give all" must be Long.MAX_VALUE
+ else -> capacity.toLong().also { check(it >= 1) }
+ }
+
+ override suspend fun collect(collector: FlowCollector<T>) {
+ val subscriber = ReactiveSubscriber<T>(capacity, requestSize)
+ publisher.injectCoroutineContext(coroutineContext).subscribe(subscriber)
+ try {
+ var consumed = 0L
+ while (true) {
+ val value = subscriber.takeNextOrNull() ?: break
+ collector.emit(value)
+ if (++consumed == requestSize) {
+ consumed = 0L
+ subscriber.makeRequest()
+ }
+ }
+ } finally {
+ subscriber.cancel()
+ }
+ }
+
+ // The second channel here is used only for broadcast
+ override suspend fun collectTo(scope: ProducerScope<T>) =
+ collect(SendingCollector(scope.channel))
+}
+
+@Suppress("SubscriberImplementation")
+private class ReactiveSubscriber<T : Any>(
+ capacity: Int,
+ private val requestSize: Long
+) : Subscriber<T> {
+ private lateinit var subscription: Subscription
+ private val channel = Channel<T>(capacity)
+
+ suspend fun takeNextOrNull(): T? = channel.receiveOrNull()
+
+ override fun onNext(value: T) {
+ // Controlled by requestSize
+ require(channel.offer(value)) { "Element $value was not added to channel because it was full, $channel" }
+ }
+
+ override fun onComplete() {
+ channel.close()
+ }
+
+ override fun onError(t: Throwable?) {
+ channel.close(t)
+ }
+
+ override fun onSubscribe(s: Subscription) {
+ subscription = s
+ makeRequest()
+ }
+
+ fun makeRequest() {
+ subscription.request(requestSize)
+ }
+
+ fun cancel() {
+ subscription.cancel()
+ }
+}
+
+// ContextInjector service is implemented in `kotlinx-coroutines-reactor` module only.
+// If `kotlinx-coroutines-reactor` module is not included, the list is empty.
+private val contextInjectors: List<ContextInjector> =
+ ServiceLoader.load(ContextInjector::class.java, ContextInjector::class.java.classLoader).toList()
+
+private fun <T> Publisher<T>.injectCoroutineContext(coroutineContext: CoroutineContext) =
+ contextInjectors.fold(this) { pub, contextInjector -> contextInjector.injectCoroutineContext(pub, coroutineContext) }
+
+
+/**
+ * Adapter that transforms [Flow] into TCK-complaint [Publisher].
+ * [cancel] invocation cancels the original flow.
+ */
+@Suppress("PublisherImplementation")
+private class FlowAsPublisher<T : Any>(private val flow: Flow<T>) : Publisher<T> {
+ override fun subscribe(subscriber: Subscriber<in T>?) {
+ if (subscriber == null) throw NullPointerException()
+ subscriber.onSubscribe(FlowSubscription(flow, subscriber))
+ }
+}
+
+/** @suppress */
+@InternalCoroutinesApi
+public class FlowSubscription<T>(
+ @JvmField val flow: Flow<T>,
+ @JvmField val subscriber: Subscriber<in T>
+) : Subscription, AbstractCoroutine<Unit>(Dispatchers.Unconfined, false) {
+ private val requested = atomic(0L)
+ private val producer = atomic<CancellableContinuation<Unit>?>(null)
+
+ override fun onStart() {
+ ::flowProcessing.startCoroutineCancellable(this)
+ }
+
+ private suspend fun flowProcessing() {
+ try {
+ consumeFlow()
+ subscriber.onComplete()
+ } catch (e: Throwable) {
+ try {
+ if (e is CancellationException) {
+ subscriber.onComplete()
+ } else {
+ subscriber.onError(e)
+ }
+ } catch (e: Throwable) {
+ // Last ditch report
+ handleCoroutineException(coroutineContext, e)
+ }
+ }
+ }
+
+ /*
+ * This method has at most one caller at any time (triggered from the `request` method)
+ */
+ private suspend fun consumeFlow() {
+ flow.collect { value ->
+ /*
+ * Flow is scopeless, thus if it's not active, its subscription was cancelled.
+ * No intermediate "child failed, but flow coroutine is not" states are allowed.
+ */
+ coroutineContext.ensureActive()
+ if (requested.value <= 0L) {
+ suspendCancellableCoroutine<Unit> {
+ producer.value = it
+ if (requested.value != 0L) it.resumeSafely()
+ }
+ }
+ requested.decrementAndGet()
+ subscriber.onNext(value)
+ }
+ }
+
+ override fun cancel() {
+ cancel(null)
+ }
+
+ override fun request(n: Long) {
+ if (n <= 0) {
+ return
+ }
+ start()
+ requested.update { value ->
+ val newValue = value + n
+ if (newValue <= 0L) Long.MAX_VALUE else newValue
+ }
+ val producer = producer.getAndSet(null) ?: return
+ producer.resumeSafely()
+ }
+
+ private fun CancellableContinuation<Unit>.resumeSafely() {
+ val token = tryResume(Unit)
+ if (token != null) {
+ completeResume(token)
+ }
+ }
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt b/reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt
new file mode 100644
index 00000000..86334928
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016-2019 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.Test
+import org.reactivestreams.*
+import kotlin.test.*
+
+class FlowAsPublisherTest : TestBase() {
+
+ @Test
+ fun testErrorOnCancellationIsReported() {
+ expect(1)
+ flow<Int> {
+ emit(2)
+ try {
+ hang { expect(3) }
+ } finally {
+ throw TestException()
+ }
+ }.asPublisher().subscribe(object : Subscriber<Int> {
+ private lateinit var subscription: Subscription
+
+ override fun onComplete() {
+ expectUnreached()
+ }
+
+ override fun onSubscribe(s: Subscription?) {
+ subscription = s!!
+ subscription.request(2)
+ }
+
+ override fun onNext(t: Int) {
+ expect(t)
+ subscription.cancel()
+ }
+
+ override fun onError(t: Throwable?) {
+ assertTrue(t is TestException)
+ expect(4)
+ }
+ })
+ finish(5)
+ }
+
+ @Test
+ fun testCancellationIsNotReported() {
+ expect(1)
+ flow<Int> {
+ emit(2)
+ hang { expect(3) }
+ }.asPublisher().subscribe(object : Subscriber<Int> {
+ private lateinit var subscription: Subscription
+
+ override fun onComplete() {
+ expect(4)
+ }
+
+ override fun onSubscribe(s: Subscription?) {
+ subscription = s!!
+ subscription.request(2)
+ }
+
+ override fun onNext(t: Int) {
+ expect(t)
+ subscription.cancel()
+ }
+
+ override fun onError(t: Throwable?) {
+ expectUnreached()
+ }
+ })
+ finish(5)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
new file mode 100644
index 00000000..aaeaa009
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.MatcherAssert.*
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.runner.*
+import org.junit.runners.*
+import org.reactivestreams.*
+import kotlin.coroutines.*
+
+@RunWith(Parameterized::class)
+class IntegrationTest(
+ private val ctx: Ctx,
+ private val delay: Boolean
+) : TestBase() {
+
+ enum class Ctx {
+ MAIN { override fun invoke(context: CoroutineContext): CoroutineContext = context.minusKey(Job) },
+ DEFAULT { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Default },
+ UNCONFINED { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Unconfined };
+
+ abstract operator fun invoke(context: CoroutineContext): CoroutineContext
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "ctx={0}, delay={1}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = Ctx.values().flatMap { ctx ->
+ listOf(false, true).map { delay ->
+ arrayOf(ctx, delay)
+ }
+ }
+ }
+
+ @Test
+ fun testEmpty(): Unit = runBlocking {
+ val pub = publish<String>(ctx(coroutineContext)) {
+ if (delay) delay(1)
+ // does not send anything
+ }
+ assertNSE { pub.awaitFirst() }
+ assertThat(pub.awaitFirstOrDefault("OK"), IsEqual("OK"))
+ assertThat(pub.awaitFirstOrNull(), IsNull())
+ assertThat(pub.awaitFirstOrElse { "ELSE" }, IsEqual("ELSE"))
+ assertNSE { pub.awaitLast() }
+ assertNSE { pub.awaitSingle() }
+ var cnt = 0
+ pub.collect { cnt++ }
+ assertThat(cnt, IsEqual(0))
+ }
+
+ @Test
+ fun testSingle() = runBlocking {
+ val pub = publish(ctx(coroutineContext)) {
+ if (delay) delay(1)
+ send("OK")
+ }
+ assertThat(pub.awaitFirst(), IsEqual("OK"))
+ assertThat(pub.awaitFirstOrDefault("!"), IsEqual("OK"))
+ assertThat(pub.awaitFirstOrNull(), IsEqual("OK"))
+ assertThat(pub.awaitFirstOrElse { "ELSE" }, IsEqual("OK"))
+ assertThat(pub.awaitLast(), IsEqual("OK"))
+ assertThat(pub.awaitSingle(), IsEqual("OK"))
+ var cnt = 0
+ pub.collect {
+ assertThat(it, IsEqual("OK"))
+ cnt++
+ }
+ assertThat(cnt, IsEqual(1))
+ }
+
+ @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)
+ }
+ }
+ assertThat(pub.awaitFirst(), IsEqual(1))
+ assertThat(pub.awaitFirstOrDefault(0), IsEqual(1))
+ assertThat(pub.awaitLast(), IsEqual(n))
+ assertThat(pub.awaitFirstOrNull(), IsEqual(1))
+ assertThat(pub.awaitFirstOrElse { 0 }, IsEqual(1))
+ assertIAE { pub.awaitSingle() }
+ 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> {
+ hang {}
+ }.awaitFirst()
+ }
+
+ job.cancel()
+ job.join()
+ }
+
+ @Test
+ fun testEmptySingle() = runTest(unhandled = listOf({e -> e is NoSuchElementException})) {
+ expect(1)
+ val job = launch(Job(), start = CoroutineStart.UNDISPATCHED) {
+ publish<String> {
+ yield()
+ expect(2)
+ // Nothing to emit
+ }.awaitFirst()
+ }
+
+ job.join()
+ finish(3)
+ }
+
+ private suspend fun checkNumbers(n: Int, pub: Publisher<Int>) {
+ var last = 0
+ pub.collect {
+ assertThat(it, IsEqual(++last))
+ }
+ assertThat(last, IsEqual(n))
+ }
+
+ private inline fun assertIAE(block: () -> Unit) {
+ try {
+ block()
+ expectUnreached()
+ } catch (e: Throwable) {
+ assertThat(e, IsInstanceOf(IllegalArgumentException::class.java))
+ }
+ }
+
+ private inline fun assertNSE(block: () -> Unit) {
+ try {
+ block()
+ expectUnreached()
+ } catch (e: Throwable) {
+ assertThat(e, IsInstanceOf(NoSuchElementException::class.java))
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt b/reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt
new file mode 100644
index 00000000..5dfd9d53
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UNCHECKED_CAST")
+
+package kotlinx.coroutines.reactive
+
+import kotlinx.coroutines.flow.*
+import org.junit.*
+import org.reactivestreams.*
+import org.reactivestreams.tck.*
+
+import org.junit.Assert.*
+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
+
+class IterableFlowTckTest : PublisherVerification<Long>(TestEnvironment()) {
+
+ private fun generate(num: Long): Array<Long> {
+ return Array(if (num >= Integer.MAX_VALUE) 1000000 else num.toInt()) { it.toLong() }
+ }
+
+ override fun createPublisher(elements: Long): Publisher<Long> {
+ return generate(elements).asIterable().asFlow().asPublisher()
+ }
+
+ @Suppress("SubscriberImplementation")
+ override fun createFailedPublisher(): Publisher<Long>? {
+ /*
+ * This is a hack for our adapter structure:
+ * Tests assume that calling "collect" is enough for publisher to fail and it is not
+ * true for our implementation
+ */
+ val pub = { error(42) }.asFlow().asPublisher()
+ return Publisher { subscriber ->
+ pub.subscribe(object : Subscriber<Long> by subscriber as Subscriber<Long> {
+ override fun onSubscribe(s: Subscription) {
+ subscriber.onSubscribe(s)
+ s.request(1)
+ }
+ })
+ }
+ }
+
+ @Test
+ fun testStackOverflowTrampoline() {
+ val latch = CountDownLatch(1)
+ val collected = ArrayList<Long>()
+ val toRequest = 1000L
+ val array = generate(toRequest)
+ val publisher = array.asIterable().asFlow().asPublisher()
+
+ publisher.subscribe(object : Subscriber<Long> {
+ private lateinit var s: Subscription
+
+ override fun onSubscribe(s: Subscription) {
+ this.s = s
+ s.request(1)
+ }
+
+ override fun onNext(aLong: Long) {
+ collected.add(aLong)
+
+ s.request(1)
+ }
+
+ override fun onError(t: Throwable) {
+
+ }
+
+ override fun onComplete() {
+ latch.countDown()
+ }
+ })
+
+ latch.await(5, TimeUnit.SECONDS)
+ assertEquals(collected, array.toList())
+ }
+
+ @Test
+ fun testConcurrentRequest() {
+ val latch = CountDownLatch(1)
+ val collected = ArrayList<Long>()
+ val n = 50000L
+ val array = generate(n)
+ val publisher = array.asIterable().asFlow().asPublisher()
+
+ publisher.subscribe(object : Subscriber<Long> {
+ private var s: Subscription? = null
+
+ override fun onSubscribe(s: Subscription) {
+ this.s = s
+ for (i in 0 until n) {
+ commonPool().execute { s.request(1) }
+ }
+ }
+
+ override fun onNext(aLong: Long) {
+ collected.add(aLong)
+ }
+
+ override fun onError(t: Throwable) {
+
+ }
+
+ override fun onComplete() {
+ latch.countDown()
+ }
+ })
+
+ latch.await(50, TimeUnit.SECONDS)
+ assertEquals(array.toList(), collected)
+ }
+
+ @Ignore
+ override fun required_spec309_requestZeroMustSignalIllegalArgumentException() {
+ }
+
+ @Ignore
+ override fun required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() {
+ }
+
+ @Ignore
+ override fun required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() {
+ // This test has a bug in it
+ }
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
new file mode 100644
index 00000000..4ffa0746
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+import org.reactivestreams.*
+
+class PublishTest : TestBase() {
+ @Test
+ fun testBasicEmpty() = runTest {
+ expect(1)
+ val publisher = publish<Int>(currentDispatcher()) {
+ expect(5)
+ }
+ expect(2)
+ publisher.subscribe(object : Subscriber<Int> {
+ override fun onSubscribe(s: Subscription?) { expect(3) }
+ override fun onNext(t: Int?) { expectUnreached() }
+ override fun onComplete() { expect(6) }
+ override fun onError(t: Throwable?) { expectUnreached() }
+ })
+ expect(4)
+ yield() // to publish coroutine
+ finish(7)
+ }
+
+ @Test
+ fun testBasicSingle() = runTest {
+ expect(1)
+ val publisher = publish(currentDispatcher()) {
+ expect(5)
+ send(42)
+ expect(7)
+ }
+ expect(2)
+ publisher.subscribe(object : Subscriber<Int> {
+ override fun onSubscribe(s: Subscription) {
+ expect(3)
+ s.request(1)
+ }
+ override fun onNext(t: Int) {
+ expect(6)
+ assertThat(t, IsEqual(42))
+ }
+ override fun onComplete() { expect(8) }
+ override fun onError(t: Throwable?) { expectUnreached() }
+ })
+ expect(4)
+ yield() // to publish coroutine
+ finish(9)
+ }
+
+ @Test
+ fun testBasicError() = runTest {
+ expect(1)
+ val publisher = publish<Int>(currentDispatcher()) {
+ expect(5)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ publisher.subscribe(object : Subscriber<Int> {
+ override fun onSubscribe(s: Subscription) {
+ expect(3)
+ s.request(1)
+ }
+ override fun onNext(t: Int) { expectUnreached() }
+ override fun onComplete() { expectUnreached() }
+ override fun onError(t: Throwable) {
+ expect(6)
+ assertThat(t, IsInstanceOf(RuntimeException::class.java))
+ assertThat(t.message, IsEqual("OK"))
+ }
+ })
+ expect(4)
+ yield() // to publish coroutine
+ finish(7)
+ }
+
+ @Test
+ fun testHandleFailureAfterCancel() = runTest {
+ expect(1)
+
+ val eh = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is RuntimeException)
+ expect(6)
+ }
+ val publisher = publish<Unit>(Dispatchers.Unconfined + eh) {
+ try {
+ expect(3)
+ delay(10000)
+ } finally {
+ expect(5)
+ throw RuntimeException("FAILED") // crash after cancel
+ }
+ }
+ var sub: Subscription? = null
+ publisher.subscribe(object : Subscriber<Unit> {
+ override fun onComplete() {
+ expectUnreached()
+ }
+
+ override fun onSubscribe(s: Subscription) {
+ expect(2)
+ sub = s
+ }
+
+ override fun onNext(t: Unit?) {
+ expectUnreached()
+ }
+
+ override fun onError(t: Throwable?) {
+ expectUnreached()
+ }
+ })
+ expect(4)
+ sub!!.cancel()
+ finish(7)
+ }
+
+ @Test
+ fun testOnNextError() = runTest {
+ expect(1)
+ val publisher = publish(currentDispatcher()) {
+ expect(4)
+ try {
+ send("OK")
+ } catch(e: Throwable) {
+ expect(6)
+ assert(e is TestException)
+ }
+ }
+ expect(2)
+ val latch = CompletableDeferred<Unit>()
+ publisher.subscribe(object : Subscriber<String> {
+ override fun onComplete() {
+ expectUnreached()
+ }
+
+ override fun onSubscribe(s: Subscription) {
+ expect(3)
+ s.request(1)
+ }
+
+ 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)
+ }
+ })
+ latch.await()
+ finish(8)
+ }
+
+ @Test
+ fun testFailingConsumer() = runTest {
+ val pub = publish(currentDispatcher()) {
+ repeat(3) {
+ expect(it + 1) // expect(1), expect(2) *should* be invoked
+ send(it)
+ }
+ }
+ try {
+ pub.collect {
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testIllegalArgumentException() {
+ assertFailsWith<IllegalArgumentException> { publish<Int>(Job()) { } }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt
new file mode 100644
index 00000000..a37719de
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016-2019 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.flow.*
+import kotlin.test.*
+
+class PublisherAsFlowTest : TestBase() {
+ @Test
+ fun testCancellation() = runTest {
+ var onNext = 0
+ var onCancelled = 0
+ var onError = 0
+
+ val publisher = publish(currentDispatcher()) {
+ coroutineContext[Job]?.invokeOnCompletion {
+ if (it is CancellationException) ++onCancelled
+ }
+
+ repeat(100) {
+ send(it)
+ }
+ }
+
+ publisher.asFlow().launchIn(CoroutineScope(Dispatchers.Unconfined)) {
+ onEach {
+ ++onNext
+ throw RuntimeException()
+ }
+ catch<Throwable> {
+ ++onError
+ }
+ }.join()
+
+
+ assertEquals(1, onNext)
+ assertEquals(1, onError)
+ assertEquals(1, onCancelled)
+ }
+
+ @Test
+ fun testBufferSize1() = runTest {
+ val publisher = publish(currentDispatcher()) {
+ expect(1)
+ send(3)
+
+ expect(2)
+ send(5)
+
+ expect(4)
+ send(7)
+ expect(6)
+ }
+
+ publisher.asFlow().collect {
+ expect(it)
+ }
+
+ finish(8)
+ }
+
+ @Test
+ fun testBufferSize10() = runTest {
+ val publisher = publish(currentDispatcher()) {
+ expect(1)
+ send(5)
+
+ expect(2)
+ send(6)
+
+ expect(3)
+ send(7)
+ expect(4)
+ }
+
+ publisher.asFlow().buffer(10).collect {
+ expect(it)
+ }
+
+ finish(8)
+ }
+
+ @Test
+ fun testConflated() = runTest {
+ val publisher = publish(currentDispatcher()) {
+ for (i in 1..5) send(i)
+ }
+ val list = publisher.asFlow().conflate().toList()
+ assertEquals(listOf(1, 5), list)
+ }
+
+ @Test
+ fun testProduce() = runTest {
+ val flow = publish(currentDispatcher()) { repeat(10) { send(it) } }.asFlow()
+ check((0..9).toList(), flow.produceIn(this))
+ check((0..9).toList(), flow.buffer(2).produceIn(this))
+ check((0..9).toList(), flow.buffer(Channel.UNLIMITED).produceIn(this))
+ check(listOf(0, 9), flow.conflate().produceIn(this))
+ }
+
+ private suspend fun check(expected: List<Int>, channel: ReceiveChannel<Int>) {
+ val result = ArrayList<Int>(10)
+ channel.consumeEach { result.add(it) }
+ assertEquals(expected, result)
+ }
+
+ @Test
+ fun testProduceCancellation() = runTest {
+ expect(1)
+ // publisher is an async coroutine, so it overproduces to the channel, but still gets cancelled
+ val flow = publish(currentDispatcher()) {
+ expect(3)
+ repeat(10) { value ->
+ when (value) {
+ in 0..6 -> send(value)
+ 7 -> try {
+ send(value)
+ } catch (e: CancellationException) {
+ finish(6)
+ throw e
+ }
+ else -> expectUnreached()
+ }
+ }
+ }.asFlow()
+ assertFailsWith<TestException> {
+ coroutineScope {
+ expect(2)
+ val channel = flow.produceIn(this)
+ channel.consumeEach { value ->
+ when (value) {
+ in 0..4 -> {}
+ 5 -> {
+ expect(4)
+ throw TestException()
+ }
+ else -> expectUnreached()
+ }
+ }
+ }
+ }
+ expect(5)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherBackpressureTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherBackpressureTest.kt
new file mode 100644
index 00000000..258632bc
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherBackpressureTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2019 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 PublisherBackpressureTest : TestBase() {
+ @Test
+ fun testCancelWhileBPSuspended() = runBlocking {
+ expect(1)
+ val observable = publish(currentDispatcher()) {
+ expect(5)
+ send("A") // will not suspend, because an item was requested
+ expect(7)
+ send("B") // second requested item
+ expect(9)
+ try {
+ send("C") // will suspend (no more requested)
+ } finally {
+ expect(12)
+ }
+ expectUnreached()
+ }
+ expect(2)
+ var sub: Subscription? = null
+ observable.subscribe(object : Subscriber<String> {
+ override fun onSubscribe(s: Subscription) {
+ sub = s
+ expect(3)
+ s.request(2) // request two items
+ }
+
+ override fun onNext(t: String) {
+ when (t) {
+ "A" -> expect(6)
+ "B" -> expect(8)
+ else -> error("Should not happen")
+ }
+ }
+
+ override fun onComplete() {
+ expectUnreached()
+ }
+
+ override fun onError(e: Throwable) {
+ expectUnreached()
+ }
+ })
+ expect(4)
+ yield() // yield to observable coroutine
+ expect(10)
+ sub!!.cancel() // now unsubscribe -- shall cancel coroutine (& do not signal)
+ expect(11)
+ yield() // shall perform finally in coroutine
+ finish(13)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherCompletionStressTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherCompletionStressTest.kt
new file mode 100644
index 00000000..b16310d0
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherCompletionStressTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2018 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 java.util.*
+import kotlin.coroutines.*
+
+class PublisherCompletionStressTest : TestBase() {
+ private val N_REPEATS = 10_000 * stressTestMultiplier
+
+ private fun CoroutineScope.range(context: CoroutineContext, start: Int, count: Int) = publish(context) {
+ for (x in start until start + count) send(x)
+ }
+
+ @Test
+ fun testCompletion() {
+ val rnd = Random()
+ repeat(N_REPEATS) {
+ val count = rnd.nextInt(5)
+ runBlocking {
+ withTimeout(5000) {
+ var received = 0
+ range(Dispatchers.Default, 1, count).collect { x ->
+ received++
+ if (x != received) error("$x != $received")
+ }
+ if (received != count) error("$received != $count")
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt
new file mode 100644
index 00000000..e238d396
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+
+class PublisherMultiTest : TestBase() {
+ @Test
+ fun testConcurrentStress() = runBlocking {
+ val n = 10_000 * stressTestMultiplier
+ val observable = publish {
+ // concurrent emitters (many coroutines)
+ val jobs = List(n) {
+ // launch
+ launch {
+ send(it)
+ }
+ }
+ jobs.forEach { it.join() }
+ }
+ val resultSet = mutableSetOf<Int>()
+ observable.collect {
+ assertTrue(resultSet.add(it))
+ }
+ assertThat(resultSet.size, IsEqual(n))
+ }
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt
new file mode 100644
index 00000000..ef178477
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2018 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.selects.*
+import org.junit.*
+import org.junit.Assert.*
+import org.junit.runner.*
+import org.junit.runners.*
+
+@RunWith(Parameterized::class)
+class PublisherSubscriptionSelectTest(private val request: Int) : TestBase() {
+ companion object {
+ @Parameterized.Parameters(name = "request = {0}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = listOf(0, 1, 10).map { arrayOf<Any>(it) }
+ }
+
+ @Test
+ fun testSelect() = runTest {
+ // source with n ints
+ val n = 1000 * stressTestMultiplier
+ val source = publish { repeat(n) { send(it) } }
+ var a = 0
+ var b = 0
+ // open two subs
+ val channelA = source.openSubscription(request)
+ val channelB = source.openSubscription(request)
+ loop@ while (true) {
+ val done: Int = select {
+ channelA.onReceiveOrNull {
+ if (it != null) assertEquals(a++, it)
+ if (it == null) 0 else 1
+ }
+ channelB.onReceiveOrNull {
+ if (it != null) assertEquals(b++, it)
+ if (it == null) 0 else 2
+ }
+ }
+ when (done) {
+ 0 -> break@loop
+ 1 -> {
+ val r = channelB.receiveOrNull()
+ if (r != null) assertEquals(b++, r)
+ }
+ 2 -> {
+ val r = channelA.receiveOrNull()
+ if (r != null) assertEquals(a++, r)
+ }
+ }
+ }
+
+ channelA.cancel()
+ channelB.cancel()
+ // should receive one of them fully
+ assertTrue(a == n || b == n)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/RangePublisherBufferedTest.kt b/reactive/kotlinx-coroutines-reactive/test/RangePublisherBufferedTest.kt
new file mode 100644
index 00000000..b710c590
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/RangePublisherBufferedTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactive
+
+import kotlinx.coroutines.flow.*
+import org.junit.*
+import org.reactivestreams.*
+import org.reactivestreams.example.unicast.*
+import org.reactivestreams.tck.*
+
+class RangePublisherBufferedTest :
+ PublisherVerification<Int>(TestEnvironment(50, 50))
+{
+ override fun createPublisher(elements: Long): Publisher<Int> {
+ return RangePublisher(1, elements.toInt()).asFlow().buffer(2).asPublisher()
+ }
+
+ override fun createFailedPublisher(): Publisher<Int>? {
+ return null
+ }
+
+ @Ignore
+ override fun required_spec309_requestZeroMustSignalIllegalArgumentException() {
+ }
+
+ @Ignore
+ override fun required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() {
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/RangePublisherTest.kt b/reactive/kotlinx-coroutines-reactive/test/RangePublisherTest.kt
new file mode 100644
index 00000000..72d5de5e
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/RangePublisherTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactive
+
+import org.junit.*
+import org.reactivestreams.*
+import org.reactivestreams.example.unicast.*
+import org.reactivestreams.tck.*
+
+class RangePublisherTest : PublisherVerification<Int>(TestEnvironment(50, 50)) {
+
+ override fun createPublisher(elements: Long): Publisher<Int> {
+ return RangePublisher(1, elements.toInt()).asFlow().asPublisher()
+ }
+
+ override fun createFailedPublisher(): Publisher<Int>? {
+ return null
+ }
+
+ @Ignore
+ override fun required_spec309_requestZeroMustSignalIllegalArgumentException() {
+ }
+
+ @Ignore
+ override fun required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() {
+ }
+}
+
+class RangePublisherWrappedTwiceTest : PublisherVerification<Int>(TestEnvironment(50, 50)) {
+
+ override fun createPublisher(elements: Long): Publisher<Int> {
+ return RangePublisher(1, elements.toInt()).asFlow().asPublisher().asFlow().asPublisher()
+ }
+
+ override fun createFailedPublisher(): Publisher<Int>? {
+ return null
+ }
+
+ @Ignore
+ override fun required_spec309_requestZeroMustSignalIllegalArgumentException() {
+ }
+
+ @Ignore
+ override fun required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() {
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/ReactiveStreamTckTest.kt b/reactive/kotlinx-coroutines-reactive/test/ReactiveStreamTckTest.kt
new file mode 100644
index 00000000..6816a986
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/ReactiveStreamTckTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-2018 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.reactivestreams.*
+import org.reactivestreams.tck.*
+import org.testng.*
+import org.testng.annotations.*
+
+
+class ReactiveStreamTckTest {
+
+ @Factory(dataProvider = "dispatchers")
+ fun createTests(dispatcher: Dispatcher): Array<Any> {
+ return arrayOf(ReactiveStreamTckTestSuite(dispatcher))
+ }
+
+ @DataProvider(name = "dispatchers")
+ public fun dispatchers(): Array<Array<Any>> = Dispatcher.values().map { arrayOf<Any>(it) }.toTypedArray()
+
+
+ public class ReactiveStreamTckTestSuite(
+ private val dispatcher: Dispatcher
+ ) : PublisherVerification<Long>(TestEnvironment(500, 500)) {
+
+ override fun createPublisher(elements: Long): Publisher<Long> =
+ publish(dispatcher.dispatcher) {
+ for (i in 1..elements) send(i)
+ }
+
+ override fun createFailedPublisher(): Publisher<Long> =
+ publish(dispatcher.dispatcher) {
+ throw TestException()
+ }
+
+ @Test
+ public override fun optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() {
+ throw SkipException("Skipped")
+ }
+
+ class TestException : Exception()
+ }
+}
+
+enum class Dispatcher(val dispatcher: CoroutineDispatcher) {
+ DEFAULT(Dispatchers.Default),
+ UNCONFINED(Dispatchers.Unconfined)
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/UnboundedIntegerIncrementPublisherTest.kt b/reactive/kotlinx-coroutines-reactive/test/UnboundedIntegerIncrementPublisherTest.kt
new file mode 100644
index 00000000..63d444c1
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/UnboundedIntegerIncrementPublisherTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactive
+
+import org.junit.*
+import org.reactivestreams.example.unicast.AsyncIterablePublisher
+import org.reactivestreams.Publisher
+import org.reactivestreams.example.unicast.InfiniteIncrementNumberPublisher
+import org.reactivestreams.tck.TestEnvironment
+import java.util.concurrent.Executors
+import java.util.concurrent.ExecutorService
+import org.reactivestreams.tck.PublisherVerification
+import org.testng.annotations.AfterClass
+import org.testng.annotations.BeforeClass
+import org.testng.annotations.Test
+
+@Test
+class UnboundedIntegerIncrementPublisherTest : PublisherVerification<Int>(TestEnvironment()) {
+
+ private var e: ExecutorService? = null
+
+ @BeforeClass
+ internal fun before() {
+ e = Executors.newFixedThreadPool(4)
+ }
+
+ @AfterClass
+ internal fun after() {
+ if (e != null) e!!.shutdown()
+ }
+
+ override fun createPublisher(elements: Long): Publisher<Int> {
+ return InfiniteIncrementNumberPublisher(e!!).asFlow().asPublisher()
+ }
+
+ override fun createFailedPublisher(): Publisher<Int> {
+ return AsyncIterablePublisher(object : Iterable<Int> {
+ override fun iterator(): Iterator<Int> {
+ throw RuntimeException("Error state signal!")
+ }
+ }, e!!)
+ }
+
+ override fun maxElementsFromPublisher(): Long {
+ return super.publisherUnableToSignalOnComplete()
+ }
+
+ @Ignore
+ override fun required_spec309_requestZeroMustSignalIllegalArgumentException() {
+ }
+
+ @Ignore
+ override fun required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() {
+ }
+}
diff --git a/reactive/kotlinx-coroutines-reactor/README.md b/reactive/kotlinx-coroutines-reactor/README.md
new file mode 100644
index 00000000..15314884
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/README.md
@@ -0,0 +1,42 @@
+# Module kotlinx-coroutines-reactor
+
+Utilities for [Reactor](https://projectreactor.io).
+
+Coroutine builders:
+
+| **Name** | **Result** | **Scope** | **Description**
+| --------------- | -------------------------------------- | ---------------- | ---------------
+| [mono] | `Mono` | [CoroutineScope] | Cold mono that starts coroutine on subscribe
+| [flux] | `Flux` | [CoroutineScope] | Cold flux that starts coroutine on subscribe
+
+Note that `Mono` and `Flux` are a subclass of [Reactive Streams](https://www.reactive-streams.org)
+`Publisher` and extensions for it are covered by
+[kotlinx-coroutines-reactive](../kotlinx-coroutines-reactive) module.
+
+Conversion functions:
+
+| **Name** | **Description**
+| -------- | ---------------
+| [Job.asMono][kotlinx.coroutines.Job.asMono] | Converts job to hot mono
+| [Deferred.asMono][kotlinx.coroutines.Deferred.asMono] | Converts deferred value to hot mono
+| [ReceiveChannel.asFlux][kotlinx.coroutines.channels.ReceiveChannel.asFlux] | Converts streaming channel to hot flux
+| [Scheduler.asCoroutineDispatcher][reactor.core.scheduler.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 -->
+<!--- 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
+[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
+<!--- END -->
+
+# Package kotlinx.coroutines.reactor
+
+Utilities for [Reactor](https://projectreactor.io).
diff --git a/reactive/kotlinx-coroutines-reactor/build.gradle b/reactive/kotlinx-coroutines-reactor/build.gradle
new file mode 100644
index 00000000..c73716d4
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/build.gradle
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+dependencies {
+ compile "io.projectreactor:reactor-core:$reactor_vesion"
+ compile project(':kotlinx-coroutines-reactive')
+}
+
+tasks.withType(dokka.getClass()) {
+ externalDocumentationLink {
+ url = new URL("https://projectreactor.io/docs/core/$reactor_vesion/api/")
+ packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
+ }
+}
+
+compileTestKotlin {
+ kotlinOptions.jvmTarget = "1.8"
+}
+
+compileKotlin {
+ kotlinOptions.jvmTarget = "1.8"
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/package.list b/reactive/kotlinx-coroutines-reactor/package.list
new file mode 100644
index 00000000..9809a3f5
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/package.list
@@ -0,0 +1,9 @@
+reactor.adapter
+reactor.core
+reactor.core.publisher
+reactor.core.scheduler
+reactor.util
+reactor.util.annotation
+reactor.util.concurrent
+reactor.util.context
+reactor.util.function
diff --git a/reactive/kotlinx-coroutines-reactor/resources/META-INF/services/kotlinx.coroutines.reactive.ContextInjector b/reactive/kotlinx-coroutines-reactor/resources/META-INF/services/kotlinx.coroutines.reactive.ContextInjector
new file mode 100644
index 00000000..0097ec35
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/resources/META-INF/services/kotlinx.coroutines.reactive.ContextInjector
@@ -0,0 +1 @@
+kotlinx.coroutines.reactor.ReactorContextInjector \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/src/Convert.kt b/reactive/kotlinx-coroutines-reactor/src/Convert.kt
new file mode 100644
index 00000000..cf6b65de
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/Convert.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import reactor.core.publisher.*
+import kotlin.coroutines.*
+
+/**
+ * Converts this job to the hot reactive mono that signals
+ * with [success][MonoSink.success] when the corresponding job completes.
+ *
+ * Every subscriber gets the signal at the same time.
+ * Unsubscribing from the resulting mono **does not** affect the original job in any way.
+ *
+ * **Note: This is an experimental api.** Conversion of coroutines primitives to reactive entities may change
+ * in the future to account for the concept of structured concurrency.
+ *
+ * @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
+ * [success][MonoSink.success] or [error][MonoSink.error].
+ *
+ * Every subscriber gets the same completion value.
+ * Unsubscribing from the resulting mono **does not** affect the original deferred value in any way.
+ *
+ * **Note: This is an experimental api.** Conversion of coroutines primitives to reactive entities may change
+ * in the future to account for the concept of structured concurrency.
+ *
+ * @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.
+ *
+ * **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).
+ *
+ * @param context -- the coroutine context from which the resulting flux is going to be signalled
+ */
+@ObsoleteCoroutinesApi
+public fun <T> ReceiveChannel<T>.asFlux(context: CoroutineContext = EmptyCoroutineContext): Flux<T> = flux(context) {
+ for (t in this@asFlux)
+ send(t)
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/src/Flux.kt b/reactive/kotlinx-coroutines-reactor/src/Flux.kt
new file mode 100644
index 00000000..18b84ac1
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/Flux.kt
@@ -0,0 +1,77 @@
+
+/*
+ * Copyright 2016-2019 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.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.reactive.*
+import org.reactivestreams.Publisher
+import reactor.core.CoreSubscriber
+import reactor.core.publisher.*
+import kotlin.coroutines.*
+import kotlin.internal.LowPriorityInOverloadResolution
+
+/**
+ * Creates cold reactive [Flux] 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 items with `send`. Unsubscribing cancels 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.
+ *
+ * Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
+ * `onNext` is not invoked concurrently.
+ *
+ * | **Coroutine action** | **Signal to subscriber**
+ * | -------------------------------------------- | ------------------------
+ * | `send` | `onNext`
+ * | Normal completion or `close` without cause | `onComplete`
+ * | Failure with exception or `close` with cause | `onError`
+ *
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
+ *
+ * **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.
+ */
+@ExperimentalCoroutinesApi
+public fun <T> flux(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Flux<T> {
+ require(context[Job] === null) { "Flux context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return Flux.from(reactorPublish(GlobalScope, context, block))
+}
+
+@Deprecated(
+ message = "CoroutineScope.flux is deprecated in favour of top-level flux",
+ level = DeprecationLevel.WARNING,
+ 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." }
+ val currentContext = subscriber.currentContext()
+ val reactorContext = (context[ReactorContext]?.context?.putAll(currentContext) ?: currentContext).asCoroutineContext()
+ val newContext = scope.newCoroutineContext(context + reactorContext)
+ val coroutine = PublisherCoroutine(newContext, subscriber)
+ subscriber.onSubscribe(coroutine) // do it first (before starting coroutine), to avoid unnecessary suspensions
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/src/Migration.kt b/reactive/kotlinx-coroutines-reactor/src/Migration.kt
new file mode 100644
index 00000000..c80d2690
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/Migration.kt
@@ -0,0 +1,13 @@
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.flow.*
+import reactor.core.publisher.*
+
+@Deprecated(
+ message = "Replaced in favor of ReactiveFlow extension, please import kotlinx.coroutines.reactor.* instead of kotlinx.coroutines.reactor.FlowKt",
+ level = DeprecationLevel.ERROR
+)
+@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
new file mode 100644
index 00000000..b218f6d0
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/Mono.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016-2019 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.*
+import reactor.core.*
+import reactor.core.publisher.*
+import kotlin.coroutines.*
+import kotlin.internal.*
+
+/**
+ * Creates cold [mono][Mono] that will run a given [block] in a coroutine.
+ * Every time the returned mono is subscribed, it starts a new coroutine.
+ * Coroutine returns a single, possibly null value. Unsubscribing cancels running coroutine.
+ *
+ * | **Coroutine action** | **Signal to sink**
+ * | ------------------------------------- | ------------------------
+ * | Returns a non-null value | `success(value)`
+ * | Returns a null | `success`
+ * | Failure with exception or unsubscribe | `error`
+ *
+ * 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.
+ */
+public fun <T> mono(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T?
+): Mono<T> {
+ require(context[Job] === null) { "Mono context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return monoInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.mono is deprecated in favour of top-level mono",
+ level = DeprecationLevel.WARNING,
+ 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)
+
+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 newContext = scope.newCoroutineContext(context + reactorContext)
+ val coroutine = MonoCoroutine(newContext, sink)
+ sink.onDispose(coroutine)
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+}
+
+private class MonoCoroutine<in T>(
+ parentContext: CoroutineContext,
+ private val sink: MonoSink<T>
+) : AbstractCoroutine<T>(parentContext, true), Disposable {
+ var disposed = false
+
+ override fun onCompleted(value: T) {
+ if (!disposed) {
+ if (value == null) sink.success() else sink.success(value)
+ }
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ if (!disposed) {
+ sink.error(cause)
+ } else if (!handled) {
+ handleCoroutineException(context, cause)
+ }
+ }
+
+ override fun dispose() {
+ disposed = true
+ cancel()
+ }
+
+ override fun isDisposed(): Boolean = disposed
+}
diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
new file mode 100644
index 00000000..942ba7b6
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
@@ -0,0 +1,56 @@
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import reactor.util.context.Context
+import kotlin.coroutines.*
+
+/**
+ * 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].
+ *
+ * Reactor builders [mono] and [flux] use this context element to enhance the resulting `subscriberContext`.
+ *
+ * ### Usages
+ * Passing reactor context from coroutine builder to reactor entity:
+ * ```
+ * launch(Context.of("key", "value").asCoroutineContext()) {
+ * mono {
+ * println(coroutineContext[ReactorContext]) // Prints { "key": "value" }
+ * }.subscribe()
+ * }
+ * ```
+ *
+ * Accessing modified reactor context enriched from the downstream:
+ * ```
+ * launch {
+ * mono {
+ * println(coroutineContext[ReactorContext]) // Prints { "key": "value" }
+ * }.subscriberContext(Context.of("key", "value"))
+ * .subscribe()
+ * }
+ * ```
+ *
+ * [CoroutineContext] of a suspendable function that awaits a value from [Mono] or [Flux] instance
+ * is propagated into [mono] and [flux] Reactor builders:
+ * ```
+ * launch(Context.of("key", "value").asCoroutineContext()) {
+ * assertEquals(bar().awaitFirst(), "value")
+ * }
+ *
+ * fun bar(): Mono<String> = mono {
+ * coroutineContext[ReactorContext]!!.context.get("key")
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public class ReactorContext(val context: Context) : AbstractCoroutineContextElement(ReactorContext) {
+ companion object Key : CoroutineContext.Key<ReactorContext>
+}
+
+/**
+ * Wraps the given [Context] into [ReactorContext], so it can be added to coroutine's context
+ * and later used via `coroutineContext[ReactorContext]`.
+ */
+@ExperimentalCoroutinesApi
+public fun Context.asCoroutineContext(): ReactorContext = ReactorContext(this) \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt
new file mode 100644
index 00000000..68309bbc
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt
@@ -0,0 +1,22 @@
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.reactive.*
+import org.reactivestreams.*
+import reactor.core.publisher.*
+import reactor.util.context.*
+import kotlin.coroutines.*
+
+internal class ReactorContextInjector : ContextInjector {
+ /**
+ * Injects all values from the [ReactorContext] entry of the given coroutine context
+ * into the downstream [Context] of Reactor's [Publisher] instances of [Mono] or [Flux].
+ */
+ 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)
+ else -> publisher
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt
new file mode 100644
index 00000000..6b031ed4
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.reactive.FlowSubscription
+import reactor.core.CoreSubscriber
+import reactor.core.publisher.Flux
+
+/**
+ * Converts the given flow to a cold flux.
+ * The original flow is cancelled when the flux subscriber is disposed.
+ */
+@ExperimentalCoroutinesApi
+public fun <T: Any> Flow<T>.asFlux(): Flux<T> = FlowAsFlux(this)
+
+private class FlowAsFlux<T : Any>(private val flow: Flow<T>) : Flux<T>() {
+ override fun subscribe(subscriber: CoreSubscriber<in T>?) {
+ if (subscriber == null) throw NullPointerException()
+ val hasContext = subscriber.currentContext().isEmpty
+ val source = if (hasContext) flow.flowOn(subscriber.currentContext().asCoroutineContext()) else flow
+ subscriber.onSubscribe(FlowSubscription(source, subscriber))
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/src/Scheduler.kt b/reactive/kotlinx-coroutines-reactor/src/Scheduler.kt
new file mode 100644
index 00000000..78c0c5e1
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/Scheduler.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import reactor.core.Disposable
+import reactor.core.scheduler.Scheduler
+import java.util.concurrent.TimeUnit
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Converts an instance of [Scheduler] to an implementation of [CoroutineDispatcher].
+ */
+fun Scheduler.asCoroutineDispatcher(): SchedulerCoroutineDispatcher = SchedulerCoroutineDispatcher(this)
+
+/**
+ * Implements [CoroutineDispatcher] on top of an arbitrary [Scheduler].
+ * @param scheduler a scheduler.
+ */
+public class SchedulerCoroutineDispatcher(
+ /**
+ * Underlying scheduler of current [CoroutineDispatcher].
+ */
+ public val scheduler: Scheduler
+) : CoroutineDispatcher(), Delay {
+ /** @suppress */
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ scheduler.schedule(block)
+ }
+
+ /** @suppress */
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ val disposable = scheduler.schedule({
+ with(continuation) { resumeUndispatched(Unit) }
+ }, timeMillis, TimeUnit.MILLISECONDS)
+ continuation.disposeOnCancellation(disposable.asDisposableHandle())
+ }
+
+ /** @suppress */
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle =
+ scheduler.schedule(block, timeMillis, TimeUnit.MILLISECONDS).asDisposableHandle()
+
+ /** @suppress */
+ override fun toString(): String = scheduler.toString()
+ /** @suppress */
+ override fun equals(other: Any?): Boolean = other is SchedulerCoroutineDispatcher && other.scheduler === scheduler
+ /** @suppress */
+ override fun hashCode(): Int = System.identityHashCode(scheduler)
+}
+
+private fun Disposable.asDisposableHandle(): DisposableHandle =
+ object : DisposableHandle {
+ override fun dispose() = this@asDisposableHandle.dispose()
+ } \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/BackpressureTest.kt b/reactive/kotlinx-coroutines-reactor/test/BackpressureTest.kt
new file mode 100644
index 00000000..80feaeb8
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/BackpressureTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.reactive.*
+import org.junit.Test
+import reactor.core.publisher.*
+import kotlin.test.*
+
+class BackpressureTest : TestBase() {
+ @Test
+ fun testBackpressureDropDirect() = runTest {
+ expect(1)
+ Flux.fromArray(arrayOf(1))
+ .onBackpressureDrop()
+ .collect {
+ assertEquals(1, it)
+ expect(2)
+ }
+ finish(3)
+ }
+
+ @Test
+ fun testBackpressureDropFlow() = runTest {
+ expect(1)
+ Flux.fromArray(arrayOf(1))
+ .onBackpressureDrop()
+ .asFlow()
+ .collect {
+ assertEquals(1, it)
+ expect(2)
+ }
+ finish(3)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/Check.kt b/reactive/kotlinx-coroutines-reactor/test/Check.kt
new file mode 100644
index 00000000..e092a6ca
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/Check.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import reactor.core.publisher.Flux
+import reactor.core.publisher.Mono
+
+fun <T> checkMonoValue(
+ mono: Mono<T>,
+ checker: (T) -> Unit
+) {
+ val monoValue = mono.block()
+ checker(monoValue)
+}
+
+fun checkErroneous(
+ mono: Mono<*>,
+ checker: (Throwable) -> Unit
+) {
+ try {
+ mono.block()
+ error("Should have failed")
+ } catch (e: Throwable) {
+ checker(e)
+ }
+}
+
+fun <T> checkSingleValue(
+ flux: Flux<T>,
+ checker: (T) -> Unit
+) {
+ val singleValue = flux.toIterable().single()
+ checker(singleValue)
+}
+
+fun checkErroneous(
+ flux: Flux<*>,
+ checker: (Throwable) -> Unit
+) {
+ val singleNotification = flux.materialize().toIterable().single()
+ checker(singleNotification.throwable)
+}
diff --git a/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt b/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt
new file mode 100644
index 00000000..10e05b76
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.reactive.*
+import org.junit.*
+import org.junit.Assert.*
+
+class ConvertTest : TestBase() {
+ @Test
+ fun testJobToMonoSuccess() = runBlocking {
+ expect(1)
+ val job = launch {
+ expect(3)
+ }
+ val mono = job.asMono(coroutineContext.minusKey(Job))
+ mono.subscribe {
+ expect(4)
+ }
+ expect(2)
+ yield()
+ finish(5)
+ }
+
+ @Test
+ fun testJobToMonoFail() = runBlocking {
+ expect(1)
+ val job = async(NonCancellable) {
+ expect(3)
+ throw RuntimeException("OK")
+ }
+ val mono = job.asMono(coroutineContext.minusKey(Job))
+ mono.subscribe(
+ { fail("no item should be emitted") },
+ { expect(4) }
+ )
+ expect(2)
+ yield()
+ finish(5)
+ }
+
+ @Test
+ fun testDeferredToMono() {
+ val d = GlobalScope.async {
+ delay(50)
+ "OK"
+ }
+ val mono1 = d.asMono(Dispatchers.Unconfined)
+ checkMonoValue(mono1) {
+ assertEquals("OK", it)
+ }
+ val mono2 = d.asMono(Dispatchers.Unconfined)
+ checkMonoValue(mono2) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testDeferredToMonoEmpty() {
+ val d = GlobalScope.async {
+ delay(50)
+ null
+ }
+ val mono1 = d.asMono(Dispatchers.Unconfined)
+ checkMonoValue(mono1, ::assertNull)
+ val mono2 = d.asMono(Dispatchers.Unconfined)
+ checkMonoValue(mono2, ::assertNull)
+ }
+
+ @Test
+ fun testDeferredToMonoFail() {
+ val d = GlobalScope.async {
+ delay(50)
+ throw TestRuntimeException("OK")
+ }
+ val mono1 = d.asMono(Dispatchers.Unconfined)
+ checkErroneous(mono1) {
+ check(it is TestRuntimeException && it.message == "OK") { "$it" }
+ }
+ val mono2 = d.asMono(Dispatchers.Unconfined)
+ checkErroneous(mono2) {
+ check(it is TestRuntimeException && it.message == "OK") { "$it" }
+ }
+ }
+
+ @Test
+ fun testToFlux() {
+ val c = GlobalScope.produce {
+ delay(50)
+ send("O")
+ delay(50)
+ send("K")
+ }
+ val flux = c.asFlux(Dispatchers.Unconfined)
+ checkMonoValue(flux.reduce { t1, t2 -> t1 + t2 }) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testToFluxFail() {
+ val c = GlobalScope.produce {
+ delay(50)
+ send("O")
+ delay(50)
+ throw TestException("K")
+ }
+ val flux = c.asFlux(Dispatchers.Unconfined)
+ val mono = mono(Dispatchers.Unconfined) {
+ var result = ""
+ try {
+ flux.collect { result += it }
+ } catch(e: Throwable) {
+ check(e is TestException)
+ result += e.message
+ }
+ result
+ }
+ checkMonoValue(mono) {
+ 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
new file mode 100644
index 00000000..2f8ce9ac
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/FlowAsFluxTest.kt
@@ -0,0 +1,27 @@
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.reactive.*
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import reactor.core.publisher.Mono
+import reactor.util.context.Context
+import kotlin.test.assertEquals
+
+class FlowAsFluxTest {
+ @Test
+ fun testFlowToFluxContextPropagation() = runBlocking<Unit> {
+ val flux = flow<String> {
+ (1..4).forEach { i -> emit(m(i).awaitFirst()) }
+ } .asFlux()
+ .subscriberContext(Context.of(1, "1"))
+ .subscriberContext(Context.of(2, "2", 3, "3", 4, "4"))
+ var i = 0
+ flux.subscribe { str -> i++; println(str); assertEquals(str, i.toString()) }
+ }
+
+ private fun m(i: Int): Mono<String> = mono {
+ val ctx = coroutineContext[ReactorContext]?.context
+ ctx?.getOrDefault(i, "noValue")
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxCompletionStressTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxCompletionStressTest.kt
new file mode 100644
index 00000000..6090408f
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxCompletionStressTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.junit.*
+import java.util.*
+import kotlin.coroutines.*
+
+class FluxCompletionStressTest : TestBase() {
+ private val N_REPEATS = 10_000 * stressTestMultiplier
+
+ private fun CoroutineScope.range(context: CoroutineContext, start: Int, count: Int) = flux(context) {
+ for (x in start until start + count) send(x)
+ }
+
+ @Test
+ fun testCompletion() {
+ val rnd = Random()
+ repeat(N_REPEATS) {
+ val count = rnd.nextInt(5)
+ runBlocking {
+ withTimeout(5000) {
+ var received = 0
+ range(Dispatchers.Default, 1, count).collect { x ->
+ received++
+ if (x != received) error("$x != $received")
+ }
+ if (received != count) error("$received != $count")
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxMultiTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxMultiTest.kt
new file mode 100644
index 00000000..ae23d3c2
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxMultiTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.junit.*
+import org.junit.Assert.*
+import reactor.core.publisher.*
+import java.io.*
+
+class FluxMultiTest : TestBase() {
+ @Test
+ fun testNumbers() {
+ val n = 100 * stressTestMultiplier
+ val flux = flux {
+ repeat(n) { send(it) }
+ }
+ checkMonoValue(flux.collectList()) { list ->
+ assertEquals((0 until n).toList(), list)
+ }
+ }
+
+ @Test
+ fun testConcurrentStress() {
+ val n = 10_000 * stressTestMultiplier
+ val flux = flux {
+ // concurrent emitters (many coroutines)
+ val jobs = List(n) {
+ // launch
+ launch {
+ send(it)
+ }
+ }
+ jobs.forEach { it.join() }
+ }
+ checkMonoValue(flux.collectList()) { list ->
+ assertEquals(n, list.size)
+ assertEquals((0 until n).toList(), list.sorted())
+ }
+ }
+
+ @Test
+ fun testIteratorResendUnconfined() {
+ val n = 10_000 * stressTestMultiplier
+ val flux = flux(Dispatchers.Unconfined) {
+ Flux.range(0, n).collect { send(it) }
+ }
+ checkMonoValue(flux.collectList()) { list ->
+ assertEquals((0 until n).toList(), list)
+ }
+ }
+
+ @Test
+ fun testIteratorResendPool() {
+ val n = 10_000 * stressTestMultiplier
+ val flux = flux {
+ Flux.range(0, n).collect { send(it) }
+ }
+ checkMonoValue(flux.collectList()) { list ->
+ assertEquals((0 until n).toList(), list)
+ }
+ }
+
+ @Test
+ fun testSendAndCrash() {
+ val flux = flux {
+ send("O")
+ throw IOException("K")
+ }
+ val mono = mono {
+ var result = ""
+ try {
+ flux.consumeEach { result += it }
+ } catch(e: IOException) {
+ result += e.message
+ }
+ result
+ }
+ checkMonoValue(mono) {
+ assertEquals("OK", it)
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt
new file mode 100644
index 00000000..7d8d4698
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.junit.*
+import org.junit.Assert.*
+import reactor.core.publisher.*
+import java.time.Duration.*
+
+class FluxSingleTest {
+ @Test
+ fun testSingleNoWait() {
+ val flux = flux {
+ send("OK")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleAwait() = runBlocking {
+ assertEquals("OK", Flux.just("O").awaitSingle() + "K")
+ }
+
+ @Test
+ fun testSingleEmitAndAwait() {
+ val flux = flux {
+ send(Flux.just("O").awaitSingle() + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleWithDelay() {
+ val flux = flux {
+ send(Flux.just("O").delayElements(ofMillis(50)).awaitSingle() + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleException() {
+ val flux = flux {
+ send(Flux.just("O", "K").awaitSingle() + "K")
+ }
+
+ checkErroneous(flux) {
+ assert(it is IllegalArgumentException)
+ }
+ }
+
+ @Test
+ fun testAwaitFirst() {
+ val flux = flux {
+ send(Flux.just("O", "#").awaitFirst() + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrDefault() {
+ val flux = flux {
+ send(Flux.empty<String>().awaitFirstOrDefault("O") + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrDefaultWithValues() {
+ val flux = flux {
+ send(Flux.just("O", "#").awaitFirstOrDefault("!") + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrNull() {
+ val flux = flux<String?> {
+ send(Flux.empty<String>().awaitFirstOrNull() ?: "OK")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrNullWithValues() {
+ val flux = flux {
+ send((Flux.just("O", "#").awaitFirstOrNull() ?: "!") + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrElse() {
+ val flux = flux {
+ send(Flux.empty<String>().awaitFirstOrElse { "O" } + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrElseWithValues() {
+ val flux = flux {
+ send(Flux.just("O", "#").awaitFirstOrElse { "!" } + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitLast() {
+ val flux = flux {
+ send(Flux.just("#", "O").awaitLast() + "K")
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromObservable() {
+ val flux = flux {
+ try {
+ send(Flux.error<String>(RuntimeException("O")).awaitFirst())
+ } catch (e: RuntimeException) {
+ send(Flux.just(e.message!!).awaitLast() + "K")
+ }
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromCoroutine() {
+ val flux = flux<String> {
+ error(Flux.just("O").awaitSingle() + "K")
+ }
+
+ checkErroneous(flux) {
+ assert(it is IllegalStateException)
+ assertEquals("OK", it.message)
+ }
+ }
+
+ @Test
+ fun testFluxIteration() {
+ val flux = flux {
+ var result = ""
+ Flux.just("O", "K").collect { result += it }
+ send(result)
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testFluxIterationFailure() {
+ val flux = flux {
+ try {
+ Flux.error<String>(RuntimeException("OK")).collect { fail("Should not be here") }
+ send("Fail")
+ } catch (e: RuntimeException) {
+ send(e.message!!)
+ }
+ }
+
+ checkSingleValue(flux) {
+ assertEquals("OK", it)
+ }
+ }
+}
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
new file mode 100644
index 00000000..ee26455e
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class FluxTest : TestBase() {
+ @Test
+ fun testBasicSuccess() = runBlocking {
+ expect(1)
+ val flux = flux(currentDispatcher()) {
+ expect(4)
+ send("OK")
+ }
+ expect(2)
+ flux.subscribe { value ->
+ expect(5)
+ Assert.assertThat(value, IsEqual("OK"))
+ }
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicFailure() = runBlocking {
+ expect(1)
+ val flux = flux<String>(currentDispatcher()) {
+ expect(4)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ flux.subscribe({
+ expectUnreached()
+ }, { error ->
+ expect(5)
+ Assert.assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ Assert.assertThat(error.message, IsEqual("OK"))
+ })
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicUnsubscribe() = runBlocking {
+ expect(1)
+ val flux = flux<String>(currentDispatcher()) {
+ expect(4)
+ yield() // back to main, will get cancelled
+ expectUnreached()
+ }
+ expect(2)
+ val sub = flux.subscribe({
+ expectUnreached()
+ }, {
+ expectUnreached()
+ })
+ expect(3)
+ yield() // to started coroutine
+ expect(5)
+ sub.dispose() // will cancel coroutine
+ yield()
+ finish(6)
+ }
+
+ @Test
+ fun testNotifyOnceOnCancellation() = runTest {
+ expect(1)
+ val observable =
+ flux(currentDispatcher()) {
+ expect(5)
+ send("OK")
+ try {
+ delay(Long.MAX_VALUE)
+ } catch (e: CancellationException) {
+ expect(11)
+ }
+ }
+ .doOnNext {
+ expect(6)
+ assertEquals("OK", it)
+ }
+ .doOnCancel {
+ expect(10) // notified once!
+ }
+ expect(2)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(3)
+ observable.collect {
+ expect(8)
+ assertEquals("OK", it)
+ }
+ }
+ expect(4)
+ yield() // to observable code
+ expect(7)
+ yield() // to consuming coroutines
+ expect(9)
+ job.cancel()
+ job.join()
+ finish(12)
+ }
+
+ @Test
+ fun testFailingConsumer() = runTest {
+ val pub = flux(currentDispatcher()) {
+ repeat(3) {
+ expect(it + 1) // expect(1), expect(2) *should* be invoked
+ send(it)
+ }
+ }
+ try {
+ pub.collect {
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testIllegalArgumentException() {
+ assertFailsWith<IllegalArgumentException> { flux<Int>(Job()) { } }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt b/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
new file mode 100644
index 00000000..2283d45a
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+import org.reactivestreams.*
+import reactor.core.publisher.*
+import java.time.Duration.*
+
+class MonoTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("timer-", "parallel-")
+ }
+
+ @Test
+ fun testBasicSuccess() = runBlocking {
+ expect(1)
+ val mono = mono(currentDispatcher()) {
+ expect(4)
+ "OK"
+ }
+ expect(2)
+ mono.subscribe { value ->
+ expect(5)
+ assertThat(value, IsEqual("OK"))
+ }
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicFailure() = runBlocking {
+ expect(1)
+ val mono = mono(currentDispatcher()) {
+ expect(4)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ mono.subscribe({
+ expectUnreached()
+ }, { error ->
+ expect(5)
+ assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ assertThat(error.message, IsEqual("OK"))
+ })
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicEmpty() = runBlocking {
+ expect(1)
+ val mono = mono(currentDispatcher()) {
+ expect(4)
+ null
+ }
+ expect(2)
+ mono.subscribe({}, { throw it }, {
+ expect(5)
+ })
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicUnsubscribe() = runBlocking {
+ expect(1)
+ val mono = mono(currentDispatcher()) {
+ expect(4)
+ yield() // back to main, will get cancelled
+ expectUnreached()
+ }
+ expect(2)
+ // nothing is called on a disposed mono
+ val sub = mono.subscribe({
+ expectUnreached()
+ }, {
+ expectUnreached()
+ })
+ expect(3)
+ yield() // to started coroutine
+ expect(5)
+ sub.dispose() // will cancel coroutine
+ yield()
+ finish(6)
+ }
+
+ @Test
+ fun testMonoNoWait() {
+ val mono = mono {
+ "OK"
+ }
+
+ checkMonoValue(mono) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testMonoAwait() = runBlocking {
+ assertEquals("OK", Mono.just("O").awaitSingle() + "K")
+ }
+
+ @Test
+ fun testMonoEmitAndAwait() {
+ val mono = mono {
+ Mono.just("O").awaitSingle() + "K"
+ }
+
+ checkMonoValue(mono) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testMonoWithDelay() {
+ val mono = mono {
+ Flux.just("O").delayElements(ofMillis(50)).awaitSingle() + "K"
+ }
+
+ checkMonoValue(mono) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testMonoException() {
+ val mono = mono {
+ Flux.just("O", "K").awaitSingle() + "K"
+ }
+
+ checkErroneous(mono) {
+ assert(it is IllegalArgumentException)
+ }
+ }
+
+ @Test
+ fun testAwaitFirst() {
+ val mono = mono {
+ Flux.just("O", "#").awaitFirst() + "K"
+ }
+
+ checkMonoValue(mono) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitLast() {
+ val mono = mono {
+ Flux.just("#", "O").awaitLast() + "K"
+ }
+
+ checkMonoValue(mono) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromFlux() {
+ val mono = mono {
+ try {
+ Flux.error<String>(RuntimeException("O")).awaitFirst()
+ } catch (e: RuntimeException) {
+ Flux.just(e.message!!).awaitLast() + "K"
+ }
+ }
+
+ checkMonoValue(mono) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromCoroutine() {
+ val mono = mono<String> {
+ throw IllegalStateException(Flux.just("O").awaitSingle() + "K")
+ }
+
+ checkErroneous(mono) {
+ assert(it is IllegalStateException)
+ assertEquals("OK", it.message)
+ }
+ }
+
+ @Test
+ fun testSuppressedException() = runTest {
+ val mono = mono(currentDispatcher()) {
+ launch(start = CoroutineStart.ATOMIC) {
+ throw TestException() // child coroutine fails
+ }
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException2() // but parent throws another exception while cleaning up
+ }
+ }
+ try {
+ mono.awaitSingle()
+ expectUnreached()
+ } catch (e: TestException) {
+ assertTrue(e.suppressed[0] is TestException2)
+ }
+ }
+
+ @Test
+ fun testUnhandledException() = runTest {
+ expect(1)
+ var subscription: Subscription? = null
+ val mono = mono(currentDispatcher() + CoroutineExceptionHandler { _, t ->
+ assertTrue(t is TestException)
+ expect(5)
+
+ }) {
+ expect(4)
+ subscription!!.cancel() // cancel our own subscription, so that delay will get cancelled
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException() // would not be able to handle it since mono is disposed
+ }
+ }
+ mono.subscribe(object : Subscriber<Unit> {
+ override fun onSubscribe(s: Subscription) {
+ expect(2)
+ subscription = s
+ }
+ override fun onNext(t: Unit?) { expectUnreached() }
+ override fun onComplete() { expectUnreached() }
+ override fun onError(t: Throwable) { expectUnreached() }
+ })
+ expect(3)
+ yield() // run coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testIllegalArgumentException() {
+ assertFailsWith<IllegalArgumentException> { mono(Job()) { } }
+ }
+}
diff --git a/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt b/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt
new file mode 100644
index 00000000..e9ac200f
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt
@@ -0,0 +1,111 @@
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.reactive.*
+import org.junit.Test
+import reactor.core.publisher.*
+import reactor.util.context.*
+import kotlin.test.*
+
+class ReactorContextTest {
+ @Test
+ fun testMonoHookedContext() = runBlocking {
+ val mono = mono(Context.of(1, "1", 7, "7").asCoroutineContext()) {
+ val ctx = coroutineContext[ReactorContext]?.context
+ 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")
+ }
+
+ @Test
+ fun testFluxContext() = runBlocking<Unit> {
+ val flux = flux(Context.of(1, "1", 7, "7").asCoroutineContext()) {
+ val ctx = coroutineContext[ReactorContext]!!.context
+ (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") }
+ var i = 0
+ flux.subscribe { str -> i++; assertEquals(str, i.toString()) }
+ }
+
+ @Test
+ fun testAwait() = runBlocking(Context.of(3, "3").asCoroutineContext()) {
+ val result = mono(Context.of(1, "1").asCoroutineContext()) {
+ val ctx = coroutineContext[ReactorContext]?.context
+ buildString {
+ (1..3).forEach { append(ctx?.getOrDefault(it, "noValue")) }
+ }
+ } .subscriberContext(Context.of(2, "2"))
+ .awaitFirst()
+ assertEquals(result, "123")
+ }
+
+ @Test
+ fun testMonoAwaitContextPropagation() = runBlocking(Context.of(7, "7").asCoroutineContext()) {
+ assertEquals(m().awaitFirst(), "7")
+ assertEquals(m().awaitFirstOrDefault("noValue"), "7")
+ assertEquals(m().awaitFirstOrNull(), "7")
+ assertEquals(m().awaitFirstOrElse { "noValue" }, "7")
+ assertEquals(m().awaitLast(), "7")
+ assertEquals(m().awaitSingle(), "7")
+ }
+
+ @Test
+ fun testFluxAwaitContextPropagation() = runBlocking<Unit>(
+ Context.of(1, "1", 2, "2", 3, "3").asCoroutineContext()
+ ) {
+ assertEquals(f().awaitFirst(), "1")
+ assertEquals(f().awaitFirstOrDefault("noValue"), "1")
+ assertEquals(f().awaitFirstOrNull(), "1")
+ assertEquals(f().awaitFirstOrElse { "noValue" }, "1")
+ assertEquals(f().awaitLast(), "3")
+ var i = 0
+ f().subscribe { str -> i++; assertEquals(str, i.toString()) }
+ }
+
+ private fun m(): Mono<String> = mono {
+ val ctx = coroutineContext[ReactorContext]?.context
+ ctx?.getOrDefault(7, "noValue")
+ }
+
+
+ private fun f(): Flux<String?> = flux {
+ val ctx = coroutineContext[ReactorContext]?.context
+ (1..3).forEach { send(ctx?.getOrDefault(it, "noValue")) }
+ }
+
+ @Test
+ fun testFlowToFluxContextPropagation() = runBlocking(
+ Context.of(1, "1", 2, "2", 3, "3").asCoroutineContext()
+ ) {
+ var i = 0
+ // call "collect" on the converted Flow
+ bar().collect { str ->
+ i++; assertEquals(str, i.toString())
+ }
+ assertEquals(i, 3)
+ }
+
+ @Test
+ fun testFlowToFluxDirectContextPropagation() = runBlocking(
+ Context.of(1, "1", 2, "2", 3, "3").asCoroutineContext()
+ ) {
+ var i = 0
+ // convert resulting flow to channel using "produceIn"
+ val channel = bar().produceIn(this)
+ channel.consumeEach { str ->
+ i++; assertEquals(str, i.toString())
+ }
+ assertEquals(i, 3)
+ }
+
+ private fun bar(): Flow<String> = flux {
+ val ctx = coroutineContext[ReactorContext]!!.context
+ (1..3).forEach { send(ctx.getOrDefault(it, "noValue")) }
+ }.asFlow()
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/SchedulerTest.kt b/reactive/kotlinx-coroutines-reactor/test/SchedulerTest.kt
new file mode 100644
index 00000000..8bc72c2f
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/SchedulerTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import org.hamcrest.core.IsEqual
+import org.hamcrest.core.IsNot
+import org.junit.Assert.assertThat
+import org.junit.Before
+import org.junit.Test
+import reactor.core.scheduler.Schedulers
+
+class SchedulerTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("single-")
+ }
+
+ @Test
+ fun testSingleScheduler(): Unit = runBlocking {
+ expect(1)
+ val mainThread = Thread.currentThread()
+ withContext(Schedulers.single().asCoroutineDispatcher()) {
+ val t1 = Thread.currentThread()
+ assertThat(t1, IsNot(IsEqual(mainThread)))
+ expect(2)
+ delay(100)
+ val t2 = Thread.currentThread()
+ assertThat(t2, IsNot(IsEqual(mainThread)))
+ expect(3)
+ }
+ finish(4)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx-example/build.gradle b/reactive/kotlinx-coroutines-rx-example/build.gradle
new file mode 100644
index 00000000..9ca90ede
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx-example/build.gradle
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+apply plugin: 'application'
+
+mainClassName = 'MainKt'
+
+dependencies {
+ ext.retrofit_version = '2.3.0'
+ compile project(':kotlinx-coroutines-rx2')
+ compile "com.squareup.retrofit2:retrofit:$retrofit_version"
+ compile "com.squareup.retrofit2:converter-gson:$retrofit_version"
+ compile "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version"
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx-example/src/main.kt b/reactive/kotlinx-coroutines-rx-example/src/main.kt
new file mode 100644
index 00000000..0c306ce9
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx-example/src/main.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import io.reactivex.Single
+import kotlinx.coroutines.*
+import kotlinx.coroutines.rx2.await
+import retrofit2.Retrofit
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
+import retrofit2.converter.gson.GsonConverterFactory
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+interface GitHub {
+ @GET("/repos/{owner}/{repo}/contributors")
+ fun contributors(
+ @Path("owner") owner: String,
+ @Path("repo") repo: String
+ ): Single<List<Contributor>>
+
+ @GET("users/{user}/repos")
+ fun listRepos(@Path("user") user: String): Single<List<Repo>>
+}
+
+data class Contributor(val login: String, val contributions: Int)
+data class Repo(val name: String)
+
+fun main(args: Array<String>) = runBlocking {
+ println("Making GitHub API request")
+
+ val retrofit = Retrofit.Builder().apply {
+ baseUrl("https://api.github.com")
+ addConverterFactory(GsonConverterFactory.create())
+ addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ }.build()
+
+ val github = retrofit.create(GitHub::class.java)
+
+ val contributors =
+ github.contributors("JetBrains", "Kotlin")
+ .await().take(10)
+
+ for ((name, contributions) in contributors) {
+ println("$name has $contributions contributions, other repos: ")
+
+ val otherRepos =
+ github.listRepos(name).await()
+ .map(Repo::name).joinToString(", ")
+
+ println(otherRepos)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/README.md b/reactive/kotlinx-coroutines-rx2/README.md
new file mode 100644
index 00000000..fbdf1b35
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/README.md
@@ -0,0 +1,78 @@
+# Module kotlinx-coroutines-rx2
+
+Utilities for [RxJava 2.x](https://github.com/ReactiveX/RxJava).
+
+Coroutine builders:
+
+| **Name** | **Result** | **Scope** | **Description**
+| --------------- | --------------------------------------- | ---------------- | ---------------
+| [rxCompletable] | `Completable` | [CoroutineScope] | Cold completable that starts coroutine on subscribe
+| [rxMaybe] | `Maybe` | [CoroutineScope] | Cold maybe that starts coroutine on subscribe
+| [rxSingle] | `Single` | [CoroutineScope] | Cold single that starts coroutine on subscribe
+| [rxObservable] | `Observable` | [ProducerScope] | Cold observable that starts coroutine on subscribe
+| [rxFlowable] | `Flowable` | [ProducerScope] | Cold observable that starts coroutine on subscribe with **backpressure** support
+
+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]
+| [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
+| [ObservableSource.awaitFirstOrElse][io.reactivex.ObservableSource.awaitFirstOrElse] | Awaits for the first value from the given observable or default from a function
+| [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
+[kotlinx-coroutines-reactive](../kotlinx-coroutines-reactive) module.
+
+Conversion functions:
+
+| **Name** | **Description**
+| -------- | ---------------
+| [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
+<!--- 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
+[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
+<!--- END -->
+
+# Package kotlinx.coroutines.rx2
+
+Utilities for [RxJava 2.x](https://github.com/ReactiveX/RxJava).
diff --git a/reactive/kotlinx-coroutines-rx2/build.gradle b/reactive/kotlinx-coroutines-rx2/build.gradle
new file mode 100644
index 00000000..8fa85092
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+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"
+}
+
+tasks.withType(dokka.getClass()) {
+ externalDocumentationLink {
+ url = new URL('http://reactivex.io/RxJava/javadoc/')
+ packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
+ }
+}
+
+task testNG(type: Test) {
+ useTestNG()
+ reports.html.destination = file("$buildDir/reports/testng")
+ include '**/*ReactiveStreamTckTest.*'
+ // Skip testNG when tests are filtered with --tests, otherwise it simply fails
+ onlyIf {
+ filter.includePatterns.isEmpty()
+ }
+ doFirst {
+ // Classic gradle, nothing works without doFirst
+ println "TestNG tests: ($includes)"
+ }
+}
+
+test {
+ dependsOn(testNG)
+ reports.html.destination = file("$buildDir/reports/junit")
+}
diff --git a/reactive/kotlinx-coroutines-rx2/package.list b/reactive/kotlinx-coroutines-rx2/package.list
new file mode 100644
index 00000000..5be2c9e9
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/package.list
@@ -0,0 +1,14 @@
+io.reactivex
+io.reactivex.annotations
+io.reactivex.disposables
+io.reactivex.exceptions
+io.reactivex.flowables
+io.reactivex.functions
+io.reactivex.observables
+io.reactivex.observers
+io.reactivex.parallel
+io.reactivex.plugins
+io.reactivex.processors
+io.reactivex.schedulers
+io.reactivex.subjects
+io.reactivex.subscribers
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt
new file mode 100644
index 00000000..ce1040e9
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2016-2018 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.Disposable
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.suspendCancellableCoroutine
+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.
+ *
+ * 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].
+ */
+public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine { cont ->
+ subscribe(object : CompletableObserver {
+ override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
+ override fun onComplete() { cont.resume(Unit) }
+ override fun onError(e: Throwable) { cont.resumeWithException(e) }
+ })
+}
+
+// ------------------------ MaybeSource ------------------------
+
+/**
+ * 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.
+ *
+ * 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].
+ */
+@Suppress("UNCHECKED_CAST")
+public suspend fun <T> MaybeSource<T>.await(): T? = (this as MaybeSource<T?>).awaitOrDefault(null)
+
+/**
+ * Awaits for completion of the maybe without blocking a thread.
+ * Returns the resulting value, [default] if no value was produced or throws the corresponding exception if this
+ * maybe had produced 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].
+ */
+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) }
+ })
+}
+
+// ------------------------ 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.
+ *
+ * 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].
+ */
+public suspend fun <T> SingleSource<T>.await(): T = suspendCancellableCoroutine { cont ->
+ subscribe(object : SingleObserver<T> {
+ override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
+ override fun onSuccess(t: T) { cont.resume(t) }
+ override fun onError(error: Throwable) { cont.resumeWithException(error) }
+ })
+}
+
+// ------------------------ 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.
+ *
+ * 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].
+ *
+ * @throws NoSuchElementException if 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.
+ *
+ * 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].
+ */
+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.
+ *
+ * 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].
+ */
+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.
+ *
+ * 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].
+ */
+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.
+ *
+ * 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].
+ *
+ * @throws NoSuchElementException if 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.
+ *
+ * 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].
+ *
+ * @throws NoSuchElementException if observable does not emit any value
+ * @throws IllegalArgumentException if observable emits more than one value
+ */
+public suspend fun <T> ObservableSource<T>.awaitSingle(): T = awaitOne(Mode.SINGLE)
+
+// ------------------------ private ------------------------
+
+internal fun CancellableContinuation<*>.disposeOnCancellation(d: Disposable) =
+ invokeOnCancellation { d.dispose() }
+
+private enum class Mode(val s: String) {
+ FIRST("awaitFirst"),
+ FIRST_OR_DEFAULT("awaitFirstOrDefault"),
+ LAST("awaitLast"),
+ SINGLE("awaitSingle");
+ override fun toString(): String = s
+}
+
+private suspend fun <T> ObservableSource<T>.awaitOne(
+ mode: Mode,
+ default: T? = null
+): T = suspendCancellableCoroutine { cont ->
+ subscribe(object : Observer<T> {
+ private lateinit var subscription: Disposable
+ private var value: T? = null
+ private var seenValue = false
+
+ override fun onSubscribe(sub: Disposable) {
+ subscription = sub
+ cont.invokeOnCancellation { sub.dispose() }
+ }
+
+ override fun onNext(t: T) {
+ when (mode) {
+ Mode.FIRST, Mode.FIRST_OR_DEFAULT -> {
+ if (!seenValue) {
+ seenValue = true
+ cont.resume(t)
+ subscription.dispose()
+ }
+ }
+ Mode.LAST, Mode.SINGLE -> {
+ if (mode == Mode.SINGLE && seenValue) {
+ if (cont.isActive)
+ cont.resumeWithException(IllegalArgumentException("More than one onNext value for $mode"))
+ subscription.dispose()
+ } else {
+ value = t
+ seenValue = true
+ }
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun onComplete() {
+ if (seenValue) {
+ if (cont.isActive) cont.resume(value as T)
+ return
+ }
+ when {
+ mode == Mode.FIRST_OR_DEFAULT -> {
+ cont.resume(default as T)
+ }
+ cont.isActive -> {
+ cont.resumeWithException(NoSuchElementException("No value received via onNext for $mode"))
+ }
+ }
+ }
+
+ override fun onError(e: Throwable) {
+ cont.resumeWithException(e)
+ }
+ })
+}
+
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt b/reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt
new file mode 100644
index 00000000..d76f1231
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx2
+
+import io.reactivex.functions.*
+import kotlinx.coroutines.*
+
+internal class RxCancellable(private val job: Job) : Cancellable {
+ override fun cancel() {
+ job.cancel()
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt
new file mode 100644
index 00000000..b038e539
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016-2018 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.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.internal.*
+
+/**
+ * Subscribes to this [MaybeSource] and returns a channel to receive elements emitted by it.
+ * The resulting channel shall be [cancelled][ReceiveChannel.cancel] to unsubscribe from this source.
+ *
+ * **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
+@Suppress("CONFLICTING_OVERLOADS")
+public fun <T> MaybeSource<T>.openSubscription(): ReceiveChannel<T> {
+ val channel = SubscriptionChannel<T>()
+ subscribe(channel)
+ return channel
+}
+
+/**
+ * Subscribes to this [ObservableSource] and returns a channel to receive elements emitted by it.
+ * The resulting channel shall be [cancelled][ReceiveChannel.cancel] to unsubscribe from this source.
+ *
+ * **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
+@Suppress("CONFLICTING_OVERLOADS")
+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.WARNING, replaceWith = ReplaceWith("this.collect(action)"))
+public suspend inline fun <T> MaybeSource<T>.consumeEach(action: (T) -> 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.WARNING, replaceWith = ReplaceWith("this.collect(action)"))
+public suspend inline fun <T> ObservableSource<T>.consumeEach(action: (T) -> 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.
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public suspend inline fun <T> MaybeSource<T>.collect(action: (T) -> 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.
+ */
+@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
+public suspend inline fun <T> ObservableSource<T>.collect(action: (T) -> Unit) =
+ openSubscription().consumeEach(action)
+
+@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+private class SubscriptionChannel<T> :
+ LinkedListChannel<T>(), Observer<T>, MaybeObserver<T>
+{
+ private val _subscription = atomic<Disposable?>(null)
+
+ @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER")
+ override fun onClosedIdempotent(closed: LockFreeLinkedListNode) {
+ _subscription.getAndSet(null)?.dispose() // dispose exactly once
+ }
+
+ // Observer overrider
+ override fun onSubscribe(sub: Disposable) {
+ _subscription.value = sub
+ }
+
+ override fun onSuccess(t: T) {
+ offer(t)
+ }
+
+ override fun onNext(t: T) {
+ offer(t)
+ }
+
+ override fun onComplete() {
+ close(cause = null)
+ }
+
+ override fun onError(e: Throwable) {
+ close(cause = e)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt b/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
new file mode 100644
index 00000000..0da06776
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2019 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.
+ * Every time the returned completable is subscribed, it starts a new coroutine.
+ * Unsubscribing cancels running coroutine.
+ *
+ * | **Coroutine action** | **Signal to subscriber**
+ * | ------------------------------------- | ------------------------
+ * | Completes successfully | `onCompleted`
+ * | Failure with exception or unsubscribe | `onError`
+ *
+ * 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.
+ */
+public fun rxCompletable(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
+): Completable {
+ require(context[Job] === null) { "Completable context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return rxCompletableInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxCompletable is deprecated in favour of top-level rxCompletable",
+ level = DeprecationLevel.WARNING,
+ 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,
+ block: suspend CoroutineScope.() -> Unit
+): Completable = Completable.create { subscriber ->
+ val newContext = scope.newCoroutineContext(context)
+ val coroutine = RxCompletableCoroutine(newContext, subscriber)
+ subscriber.setCancellable(RxCancellable(coroutine))
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+}
+
+private class RxCompletableCoroutine(
+ parentContext: CoroutineContext,
+ private val subscriber: CompletableEmitter
+) : AbstractCoroutine<Unit>(parentContext, true) {
+ override fun onCompleted(value: Unit) {
+ try {
+ if (!subscriber.isDisposed) subscriber.onComplete()
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ if (!subscriber.isDisposed) {
+ try {
+ subscriber.onError(cause)
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
+ } else if (!handled) {
+ handleCoroutineException(context, cause)
+ }
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt
new file mode 100644
index 00000000..4b121271
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.*
+
+/**
+ * Converts this job to the hot reactive completable that signals
+ * with [onCompleted][CompletableSubscriber.onCompleted] when the corresponding job completes.
+ *
+ * Every subscriber gets the signal at the same time.
+ * Unsubscribing from the resulting completable **does not** affect the original job in any way.
+ *
+ * **Note: This is an experimental api.** Conversion of coroutines primitives to reactive entities may change
+ * in the future to account for the concept of structured concurrency.
+ *
+ * @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()
+}
+
+/**
+ * Converts this deferred value to the hot reactive maybe that signals
+ * [onComplete][MaybeEmitter.onComplete], [onSuccess][MaybeEmitter.onSuccess] or [onError][MaybeEmitter.onError].
+ *
+ * Every subscriber gets the same completion value.
+ * Unsubscribing from the resulting maybe **does not** affect the original deferred value in any way.
+ *
+ * **Note: This is an experimental api.** Conversion of coroutines primitives to reactive entities may change
+ * in the future to account for the concept of structured concurrency.
+ *
+ * @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()
+}
+
+/**
+ * Converts this deferred value to the hot reactive single that signals either
+ * [onSuccess][SingleSubscriber.onSuccess] or [onError][SingleSubscriber.onError].
+ *
+ * Every subscriber gets the same completion value.
+ * Unsubscribing from the resulting single **does not** affect the original deferred value in any way.
+ *
+ * **Note: This is an experimental api.** Conversion of coroutines primitives to reactive entities may change
+ * in the future to account for the concept of structured concurrency.
+ *
+ * @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()
+}
+
+/**
+ * Converts a stream of elements received from the channel to the hot reactive observable.
+ *
+ * Every subscriber receives values from this channel in **fan-out** fashion. If the are multiple subscribers,
+ * they'll receive values in round-robin way.
+ *
+ * **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).
+ *
+ * @param context -- the coroutine context from which the resulting observable is going to be signalled
+ */
+@ObsoleteCoroutinesApi
+public fun <T : Any> ReceiveChannel<T>.asObservable(context: CoroutineContext): Observable<T> = rxObservable(context) {
+ for (t in this@asObservable)
+ send(t)
+}
+
+/**
+ * Converts the given flow to a cold observable.
+ * The original flow is cancelled when the observable subscriber is disposed.
+ */
+@JvmName("from")
+@ExperimentalCoroutinesApi
+public fun <T: Any> Flow<T>.asObservable() : Observable<T> = Observable.create { emitter ->
+ /*
+ * ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if
+ * asObservable is already invoked from unconfined
+ */
+ val job = GlobalScope.launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) {
+ try {
+ collect { value -> emitter.onNext(value) }
+ emitter.onComplete()
+ } catch (e: Throwable) {
+ // 'create' provides safe emitter, so we can unconditionally call on* here if exception occurs in `onComplete`
+ if (e !is CancellationException) emitter.onError(e)
+ else emitter.onComplete()
+
+ }
+ }
+ emitter.setCancellable(RxCancellable(job))
+}
+
+/**
+ * Converts the given flow to a cold flowable.
+ * The original flow is cancelled when the flowable subscriber is disposed.
+ */
+@JvmName("from")
+@ExperimentalCoroutinesApi
+public fun <T: Any> Flow<T>.asFlowable(): Flowable<T> = Flowable.fromPublisher(asPublisher())
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt b/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt
new file mode 100644
index 00000000..beee40ee
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2019 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 kotlinx.coroutines.channels.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.*
+import kotlin.internal.*
+
+/**
+ * Creates cold [flowable][Flowable] that will run a given [block] in a coroutine.
+ * Every time the returned flowable is subscribed, it starts a new coroutine.
+ * Coroutine emits items with `send`. Unsubscribing cancels running coroutine.
+ *
+ * Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
+ * `onNext` is not invoked concurrently.
+ *
+ * | **Coroutine action** | **Signal to subscriber**
+ * | -------------------------------------------- | ------------------------
+ * | `send` | `onNext`
+ * | Normal completion or `close` without cause | `onComplete`
+ * | Failure with exception or `close` with cause | `onError`
+ *
+ * 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.
+ *
+ * **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
+): Flowable<T> {
+ require(context[Job] === null) { "Flowable context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return Flowable.fromPublisher(publishInternal(GlobalScope, context, block))
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxFlowable is deprecated in favour of top-level rxFlowable",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("rxFlowable(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.rxFlowable(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Flowable<T> = Flowable.fromPublisher(publishInternal(this, context, block))
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt b/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt
new file mode 100644
index 00000000..fbc366c6
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2019 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.
+ * Every time the returned observable is subscribed, it starts a new coroutine.
+ * Coroutine returns a single, possibly null value. Unsubscribing cancels running coroutine.
+ *
+ * | **Coroutine action** | **Signal to subscriber**
+ * | ------------------------------------- | ------------------------
+ * | Returns a non-null value | `onSuccess`
+ * | Returns a null | `onComplete`
+ * | Failure with exception or unsubscribe | `onError`
+ *
+ * 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.
+ */
+public fun <T> rxMaybe(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T?
+): Maybe<T> {
+ require(context[Job] === null) { "Maybe context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return rxMaybeInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxMaybe is deprecated in favour of top-level rxMaybe",
+ level = DeprecationLevel.WARNING,
+ 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,
+ block: suspend CoroutineScope.() -> T?
+): Maybe<T> = Maybe.create { subscriber ->
+ val newContext = scope.newCoroutineContext(context)
+ val coroutine = RxMaybeCoroutine(newContext, subscriber)
+ subscriber.setCancellable(RxCancellable(coroutine))
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+}
+
+private class RxMaybeCoroutine<T>(
+ parentContext: CoroutineContext,
+ private val subscriber: MaybeEmitter<T>
+) : AbstractCoroutine<T>(parentContext, true) {
+ override fun onCompleted(value: T) {
+ if (!subscriber.isDisposed) {
+ try {
+ if (value == null) subscriber.onComplete() else subscriber.onSuccess(value)
+ } catch(e: Throwable) {
+ handleCoroutineException(context, e)
+ }
+ }
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ if (!subscriber.isDisposed) {
+ try {
+ subscriber.onError(cause)
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
+ } else if (!handled) {
+ handleCoroutineException(context, cause)
+ }
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
new file mode 100644
index 00000000..3d0ccd82
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2016-2019 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 io.reactivex.exceptions.*
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+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.
+ * Every time the returned observable is subscribed, it starts a new coroutine.
+ * Coroutine emits items with `send`. Unsubscribing cancels running coroutine.
+ *
+ * Invocations of `send` are suspended appropriately to ensure that `onNext` is not invoked concurrently.
+ * Note that Rx 2.x [Observable] **does not support backpressure**. Use [rxFlowable].
+ *
+ * | **Coroutine action** | **Signal to subscriber**
+ * | -------------------------------------------- | ------------------------
+ * | `send` | `onNext`
+ * | Normal completion or `close` without cause | `onComplete`
+ * | Failure with exception or `close` with cause | `onError`
+ *
+ * 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.
+ */
+@ExperimentalCoroutinesApi
+public fun <T : Any> rxObservable(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Observable<T> {
+ require(context[Job] === null) { "Observable context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return rxObservableInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxObservable is deprecated in favour of top-level rxObservable",
+ level = DeprecationLevel.WARNING,
+ 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,
+ block: suspend ProducerScope<T>.() -> Unit
+): Observable<T> = Observable.create { subscriber ->
+ val newContext = scope.newCoroutineContext(context)
+ val coroutine = RxObservableCoroutine(newContext, subscriber)
+ subscriber.setCancellable(RxCancellable(coroutine)) // do it first (before starting coroutine), to await unnecessary suspensions
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+}
+
+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>(
+ parentContext: CoroutineContext,
+ private val subscriber: ObservableEmitter<T>
+) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, SelectClause2<T, SendChannel<T>> {
+ override val channel: SendChannel<T> get() = this
+
+ // Mutex is locked when 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 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
+ }
+
+ 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)
+ }
+
+ override val onSend: SelectClause2<T, SendChannel<T>>
+ get() = this
+
+ // registerSelectSend
+ @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)
+ block(this)
+ }
+ }
+
+ // assert: mutex.isLocked()
+ private fun doLockedNext(elem: T) {
+ // check if already closed for send
+ if (!isActive) {
+ doLockedSignalCompleted(completionCause, completionCauseHandled)
+ throw 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
+ }
+ /*
+ * There is no sense to check for `isActive` before doing `unlock`, because cancellation/completion might
+ * happen after this check and before `unlock` (see signalCompleted that does not do anything
+ * if it fails to acquire the lock that we are still holding).
+ * We have to recheck `isCompleted` after `unlock` anyway.
+ */
+ unlockAndCheckCompleted()
+ }
+
+ private fun unlockAndCheckCompleted() {
+ mutex.unlock()
+ // recheck isActive
+ if (!isActive && mutex.tryLock())
+ doLockedSignalCompleted(completionCause, completionCauseHandled)
+ }
+
+ // assert: mutex.isLocked()
+ 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)
+ 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()) {
+ handleCoroutineException(context, cause)
+ }
+ }
+ else {
+ subscriber.onComplete()
+ }
+ } catch (e: Throwable) {
+ // Unhandled exception (cannot handle in other way, since we are already complete)
+ handleCoroutineException(context, e)
+ }
+ }
+ } finally {
+ mutex.unlock()
+ }
+ }
+
+ private fun signalCompleted(cause: Throwable?, handled: Boolean) {
+ if (!_signal.compareAndSet(OPEN, CLOSED)) return // abort, other thread invoked doLockedSignalCompleted
+ if (mutex.tryLock()) // if we can acquire the lock
+ doLockedSignalCompleted(cause, handled)
+ }
+
+ override fun onCompleted(value: Unit) {
+ signalCompleted(null, false)
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ signalCompleted(cause, handled)
+ }
+}
+
+internal fun Throwable.isFatal() = try {
+ Exceptions.throwIfFatal(this) // Rx-consistent behaviour without hardcode
+ false
+} catch (e: Throwable) {
+ true
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt b/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt
new file mode 100644
index 00000000..53fbaf65
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx2
+
+import io.reactivex.Scheduler
+import kotlinx.coroutines.*
+import java.util.concurrent.TimeUnit
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Converts an instance of [Scheduler] to an implementation of [CoroutineDispatcher]
+ * and provides native support of [delay] and [withTimeout].
+ */
+public fun Scheduler.asCoroutineDispatcher(): SchedulerCoroutineDispatcher = SchedulerCoroutineDispatcher(this)
+
+/**
+ * Implements [CoroutineDispatcher] on top of an arbitrary [Scheduler].
+ * @param scheduler a scheduler.
+ */
+public class SchedulerCoroutineDispatcher(
+ /**
+ * Underlying scheduler of current [CoroutineDispatcher].
+ */
+ public val scheduler: Scheduler
+) : CoroutineDispatcher(), Delay {
+ /** @suppress */
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ scheduler.scheduleDirect(block)
+ }
+
+ /** @suppress */
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ val disposable = scheduler.scheduleDirect({
+ with(continuation) { resumeUndispatched(Unit) }
+ }, timeMillis, TimeUnit.MILLISECONDS)
+ continuation.disposeOnCancellation(disposable)
+ }
+
+ /** @suppress */
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val disposable = scheduler.scheduleDirect(block, timeMillis, TimeUnit.MILLISECONDS)
+ return DisposableHandle { disposable.dispose() }
+ }
+
+ /** @suppress */
+ override fun toString(): String = scheduler.toString()
+ /** @suppress */
+ override fun equals(other: Any?): Boolean = other is SchedulerCoroutineDispatcher && other.scheduler === scheduler
+ /** @suppress */
+ override fun hashCode(): Int = System.identityHashCode(scheduler)
+}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt b/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt
new file mode 100644
index 00000000..b6cebf09
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2019 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.
+ * Every time the returned observable is subscribed, it starts a new coroutine.
+ * Coroutine returns a single value. Unsubscribing cancels running coroutine.
+ *
+ * | **Coroutine action** | **Signal to subscriber**
+ * | ------------------------------------- | ------------------------
+ * | Returns a value | `onSuccess`
+ * | Failure with exception or unsubscribe | `onError`
+ *
+ * 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.
+ */
+public fun <T : Any> rxSingle(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T
+): Single<T> {
+ require(context[Job] === null) { "Single context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return rxSingleInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxSingle is deprecated in favour of top-level rxSingle",
+ level = DeprecationLevel.WARNING,
+ 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,
+ block: suspend CoroutineScope.() -> T
+): Single<T> = Single.create { subscriber ->
+ val newContext = scope.newCoroutineContext(context)
+ val coroutine = RxSingleCoroutine(newContext, subscriber)
+ subscriber.setCancellable(RxCancellable(coroutine))
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+}
+
+private class RxSingleCoroutine<T: Any>(
+ parentContext: CoroutineContext,
+ private val subscriber: SingleEmitter<T>
+) : AbstractCoroutine<T>(parentContext, true) {
+ override fun onCompleted(value: T) {
+ try {
+ if (!subscriber.isDisposed) subscriber.onSuccess(value)
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
+ }
+
+ override fun onCancelled(cause: Throwable, handled: Boolean) {
+ if (!subscriber.isDisposed) {
+ try {
+ subscriber.onError(cause)
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
+ } else if (!handled) {
+ handleCoroutineException(context, cause)
+ }
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/BackpressureTest.kt b/reactive/kotlinx-coroutines-rx2/test/BackpressureTest.kt
new file mode 100644
index 00000000..ed0bc369
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/BackpressureTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 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 kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.reactive.*
+import org.junit.Test
+import kotlin.test.*
+
+class BackpressureTest : TestBase() {
+ @Test
+ fun testBackpressureDropDirect() = runTest {
+ expect(1)
+ Flowable.fromArray(1)
+ .onBackpressureDrop()
+ .collect {
+ assertEquals(1, it)
+ expect(2)
+ }
+ finish(3)
+ }
+
+ @Test
+ fun testBackpressureDropFlow() = runTest {
+ expect(1)
+ Flowable.fromArray(1)
+ .onBackpressureDrop()
+ .asFlow()
+ .collect {
+ assertEquals(1, it)
+ expect(2)
+ }
+ finish(3)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/Check.kt b/reactive/kotlinx-coroutines-rx2/test/Check.kt
new file mode 100644
index 00000000..29eda6fa
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/Check.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx2
+
+import io.reactivex.*
+
+fun <T> checkSingleValue(
+ observable: Observable<T>,
+ checker: (T) -> Unit
+) {
+ val singleValue = observable.blockingSingle()
+ checker(singleValue)
+}
+
+fun checkErroneous(
+ observable: Observable<*>,
+ checker: (Throwable) -> Unit
+) {
+ val singleNotification = observable.materialize().blockingSingle()
+ val error = singleNotification.error ?: error("Excepted error")
+ checker(error)
+}
+
+fun <T> checkSingleValue(
+ single: Single<T>,
+ checker: (T) -> Unit
+) {
+ val singleValue = single.blockingGet()
+ checker(singleValue)
+}
+
+fun checkErroneous(
+ single: Single<*>,
+ checker: (Throwable) -> Unit
+) {
+ try {
+ single.blockingGet()
+ error("Should have failed")
+ } catch (e: Throwable) {
+ checker(e)
+ }
+}
+
+fun <T> checkMaybeValue(
+ maybe: Maybe<T>,
+ checker: (T?) -> Unit
+) {
+ val maybeValue = maybe.toFlowable().blockingIterable().firstOrNull()
+ checker(maybeValue)
+}
+
+@Suppress("UNCHECKED_CAST")
+fun checkErroneous(
+ maybe: Maybe<*>,
+ checker: (Throwable) -> Unit
+) {
+ try {
+ (maybe as Maybe<Any>).blockingGet()
+ error("Should have failed")
+ } catch (e: Throwable) {
+ checker(e)
+ }
+}
+
diff --git a/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt b/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt
new file mode 100644
index 00000000..a7caea47
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2016-2018 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.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+
+class CompletableTest : TestBase() {
+ @Test
+ fun testBasicSuccess() = runBlocking {
+ expect(1)
+ val completable = rxCompletable(currentDispatcher()) {
+ expect(4)
+ }
+ expect(2)
+ completable.subscribe {
+ expect(5)
+ }
+ expect(3)
+ yield() // to completable coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicFailure() = runBlocking {
+ expect(1)
+ val completable = rxCompletable(currentDispatcher()) {
+ expect(4)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ completable.subscribe({
+ expectUnreached()
+ }, { error ->
+ expect(5)
+ assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ assertThat(error.message, IsEqual("OK"))
+ })
+ expect(3)
+ yield() // to completable coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicUnsubscribe() = runBlocking {
+ expect(1)
+ val completable = rxCompletable(currentDispatcher()) {
+ expect(4)
+ yield() // back to main, will get cancelled
+ expectUnreached()
+ }
+ expect(2)
+ // nothing is called on a disposed rx2 completable
+ val sub = completable.subscribe({
+ expectUnreached()
+ }, {
+ expectUnreached()
+ })
+ expect(3)
+ yield() // to started coroutine
+ expect(5)
+ sub.dispose() // will cancel coroutine
+ yield()
+ finish(6)
+ }
+
+ @Test
+ fun testAwaitSuccess() = runBlocking {
+ expect(1)
+ val completable = rxCompletable(currentDispatcher()) {
+ expect(3)
+ }
+ expect(2)
+ completable.await() // shall launch coroutine
+ finish(4)
+ }
+
+ @Test
+ fun testAwaitFailure() = runBlocking {
+ expect(1)
+ val completable = rxCompletable(currentDispatcher()) {
+ expect(3)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ try {
+ completable.await() // shall launch coroutine and throw exception
+ expectUnreached()
+ } catch (e: RuntimeException) {
+ finish(4)
+ assertThat(e.message, IsEqual("OK"))
+ }
+ }
+
+ @Test
+ fun testSuppressedException() = runTest {
+ val completable = rxCompletable(currentDispatcher()) {
+ launch(start = CoroutineStart.ATOMIC) {
+ throw TestException() // child coroutine fails
+ }
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException2() // but parent throws another exception while cleaning up
+ }
+ }
+ try {
+ completable.await()
+ expectUnreached()
+ } catch (e: TestException) {
+ assertTrue(e.suppressed[0] is TestException2)
+ }
+ }
+
+ @Test
+ fun testUnhandledException() = runTest() {
+ expect(1)
+ var disposable: Disposable? = null
+ val eh = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is TestException)
+ expect(5)
+ }
+ val completable = rxCompletable(currentDispatcher() + eh) {
+ expect(4)
+ disposable!!.dispose() // cancel our own subscription, so that delay will get cancelled
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException() // would not be able to handle it since mono is disposed
+ }
+ }
+ completable.subscribe(object : CompletableObserver {
+ override fun onSubscribe(d: Disposable) {
+ expect(2)
+ disposable = d
+ }
+ override fun onComplete() { expectUnreached() }
+ override fun onError(t: Throwable) { expectUnreached() }
+ })
+ expect(3)
+ yield() // run coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testFatalExceptionInSubscribe() = runTest {
+ GlobalScope.rxCompletable(Dispatchers.Unconfined + CoroutineExceptionHandler{ _, e -> assertTrue(e is LinkageError); expect(2)}) {
+ expect(1)
+ 42
+ }.subscribe({ throw LinkageError() })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionInSingle() = runTest {
+ GlobalScope.rxCompletable(Dispatchers.Unconfined) {
+ throw LinkageError()
+ }.subscribe({ expectUnreached() }, { expect(1); assertTrue(it is LinkageError) })
+ finish(2)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ConvertTest.kt b/reactive/kotlinx-coroutines-rx2/test/ConvertTest.kt
new file mode 100644
index 00000000..758b6326
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ConvertTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2016-2019 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 org.junit.*
+import org.junit.Assert.*
+
+class ConvertTest : TestBase() {
+ @Test
+ fun testToCompletableSuccess() = runBlocking {
+ expect(1)
+ val job = launch {
+ expect(3)
+ }
+ val completable = job.asCompletable(coroutineContext.minusKey(Job))
+ completable.subscribe {
+ expect(4)
+ }
+ expect(2)
+ yield()
+ finish(5)
+ }
+
+ @Test
+ fun testToCompletableFail() = runBlocking {
+ expect(1)
+ val job = async(NonCancellable) { // don't kill parent on exception
+ expect(3)
+ throw RuntimeException("OK")
+ }
+ val completable = job.asCompletable(coroutineContext.minusKey(Job))
+ completable.subscribe {
+ expect(4)
+ }
+ expect(2)
+ yield()
+ finish(5)
+ }
+
+ @Test
+ fun testToMaybe() {
+ val d = GlobalScope.async {
+ delay(50)
+ "OK"
+ }
+ val maybe1 = d.asMaybe(Dispatchers.Unconfined)
+ checkMaybeValue(maybe1) {
+ assertEquals("OK", it)
+ }
+ val maybe2 = d.asMaybe(Dispatchers.Unconfined)
+ checkMaybeValue(maybe2) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testToMaybeEmpty() {
+ val d = GlobalScope.async {
+ delay(50)
+ null
+ }
+ val maybe1 = d.asMaybe(Dispatchers.Unconfined)
+ checkMaybeValue(maybe1, ::assertNull)
+ val maybe2 = d.asMaybe(Dispatchers.Unconfined)
+ checkMaybeValue(maybe2, ::assertNull)
+ }
+
+ @Test
+ fun testToMaybeFail() {
+ val d = GlobalScope.async {
+ delay(50)
+ throw TestRuntimeException("OK")
+ }
+ val maybe1 = d.asMaybe(Dispatchers.Unconfined)
+ checkErroneous(maybe1) {
+ check(it is TestRuntimeException && it.message == "OK") { "$it" }
+ }
+ val maybe2 = d.asMaybe(Dispatchers.Unconfined)
+ checkErroneous(maybe2) {
+ check(it is TestRuntimeException && it.message == "OK") { "$it" }
+ }
+ }
+
+ @Test
+ fun testToSingle() {
+ val d = GlobalScope.async {
+ delay(50)
+ "OK"
+ }
+ val single1 = d.asSingle(Dispatchers.Unconfined)
+ checkSingleValue(single1) {
+ assertEquals("OK", it)
+ }
+ val single2 = d.asSingle(Dispatchers.Unconfined)
+ checkSingleValue(single2) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testToSingleFail() {
+ val d = GlobalScope.async {
+ delay(50)
+ throw TestRuntimeException("OK")
+ }
+ val single1 = d.asSingle(Dispatchers.Unconfined)
+ checkErroneous(single1) {
+ check(it is TestRuntimeException && it.message == "OK") { "$it" }
+ }
+ val single2 = d.asSingle(Dispatchers.Unconfined)
+ checkErroneous(single2) {
+ check(it is TestRuntimeException && it.message == "OK") { "$it" }
+ }
+ }
+
+ @Test
+ fun testToObservable() {
+ val c = GlobalScope.produce {
+ delay(50)
+ send("O")
+ delay(50)
+ send("K")
+ }
+ val observable = c.asObservable(Dispatchers.Unconfined)
+ checkSingleValue(observable.reduce { t1, t2 -> t1 + t2 }.toSingle()) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testToObservableFail() {
+ val c = GlobalScope.produce {
+ delay(50)
+ send("O")
+ delay(50)
+ throw TestException("K")
+ }
+ val observable = c.asObservable(Dispatchers.Unconfined)
+ val single = rxSingle(Dispatchers.Unconfined) {
+ var result = ""
+ try {
+ observable.collect { result += it }
+ } catch(e: Throwable) {
+ check(e is TestException)
+ result += e.message
+ }
+ result
+ }
+ checkSingleValue(single) {
+ assertEquals("OK", it)
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/FlowAsObservableTest.kt b/reactive/kotlinx-coroutines-rx2/test/FlowAsObservableTest.kt
new file mode 100644
index 00000000..ab9e4028
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/FlowAsObservableTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016-2019 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.flow.*
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+
+class FlowAsObservableTest : TestBase() {
+ @Test
+ fun testBasicSuccess() = runTest {
+ expect(1)
+ val observable = flow {
+ expect(3)
+ emit("OK")
+ }.asObservable()
+
+ expect(2)
+ observable.subscribe { value ->
+ expect(4)
+ assertEquals("OK", value)
+ }
+
+ finish(5)
+ }
+
+ @Test
+ fun testBasicFailure() = runTest {
+ expect(1)
+ val observable = flow<Int> {
+ expect(3)
+ throw RuntimeException("OK")
+ }.asObservable()
+
+ expect(2)
+ observable.subscribe({ expectUnreached() }, { error ->
+ expect(4)
+ assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ assertEquals("OK", error.message)
+ })
+ finish(5)
+ }
+
+ @Test
+ fun testBasicUnsubscribe() = runTest {
+ expect(1)
+ val observable = flow<Int> {
+ expect(3)
+ hang {
+ expect(4)
+ }
+ }.asObservable()
+
+ expect(2)
+ val sub = observable.subscribe({ expectUnreached() }, { expectUnreached() })
+ sub.dispose() // will cancel coroutine
+ finish(5)
+ }
+
+ @Test
+ fun testNotifyOnceOnCancellation() = runTest {
+ val observable =
+ flow {
+ expect(3)
+ emit("OK")
+ hang {
+ expect(7)
+ }
+ }.asObservable()
+ .doOnNext {
+ expect(4)
+ assertEquals("OK", it)
+ }
+ .doOnDispose {
+ expect(6) // notified once!
+ }
+
+ expect(1)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ observable.collect {
+ expect(5)
+ assertEquals("OK", it)
+ }
+ }
+
+ yield()
+ job.cancelAndJoin()
+ finish(8)
+ }
+
+ @Test
+ fun testFailingConsumer() = runTest {
+ expect(1)
+ val observable = flow {
+ expect(2)
+ emit("OK")
+ hang {
+ expect(4)
+ }
+
+ }.asObservable()
+
+ try {
+ observable.collect {
+ expect(3)
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ finish(5)
+ }
+ }
+
+ @Test
+ fun testNonAtomicStart() = runTest {
+ withContext(Dispatchers.Unconfined) {
+ val observable = flow<Int> {
+ expect(1)
+ }.asObservable()
+
+ val disposable = observable.subscribe({ expectUnreached() }, { expectUnreached() }, { expectUnreached() })
+ disposable.dispose()
+ }
+ finish(2)
+ }
+
+ @Test
+ fun testFlowCancelledFromWithin() = runTest {
+ val observable = flow {
+ expect(1)
+ emit(1)
+ kotlin.coroutines.coroutineContext.cancel()
+ kotlin.coroutines.coroutineContext.ensureActive()
+ expectUnreached()
+ }.asObservable()
+
+ observable.subscribe({ expect(2) }, { expectUnreached() }, { finish(3) })
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt b/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt
new file mode 100644
index 00000000..4f3e7241
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016-2019 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 org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class FlowableExceptionHandlingTest : TestBase() {
+
+ @Before
+ fun setup() {
+ ignoreLostThreads("RxComputationThreadPool-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+ }
+
+ private inline fun <reified T : Throwable> ceh(expect: Int) = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is T)
+ expect(expect)
+ }
+
+ private fun cehUnreached() = CoroutineExceptionHandler { _, _ -> expectUnreached() }
+
+ @Test
+ fun testException() = runTest {
+ rxFlowable<Int>(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ throw TestException()
+ }.subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Reported to onError
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalException() = runTest {
+ rxFlowable<Int>(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ throw LinkageError()
+ }.subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Fatal exception is reported to both onError and CEH
+ })
+ finish(4)
+ }
+
+ @Test
+ fun testExceptionAsynchronous() = runTest {
+ rxFlowable<Int>(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ throw TestException()
+ }.publish()
+ .refCount()
+ .subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Reported to onError
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionAsynchronous() = runTest {
+ rxFlowable<Int>(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ throw LinkageError()
+ }.publish()
+ .refCount()
+ .subscribe({
+ expectUnreached()
+ }, {
+ expect(2)
+ })
+ finish(4)
+ }
+
+ @Test
+ fun testFatalExceptionFromSubscribe() = runTest {
+ rxFlowable(Dispatchers.Unconfined + ceh<LinkageError>(4)) {
+ expect(1)
+ send(Unit)
+ }.subscribe({
+ expect(2)
+ throw LinkageError()
+ }, { expect(3) }) // Fatal exception is reported to both onError and CEH
+ finish(5)
+ }
+
+ @Test
+ fun testExceptionFromSubscribe() = runTest {
+ rxFlowable(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ send(Unit)
+ }.subscribe({
+ expect(2)
+ throw TestException()
+ }, { expect(3) }) // not reported to onError because came from the subscribe itself
+ finish(4)
+ }
+
+ @Test
+ fun testAsynchronousExceptionFromSubscribe() = runTest {
+ rxFlowable(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ send(Unit)
+ }.publish()
+ .refCount()
+ .subscribe({
+ expect(2)
+ throw RuntimeException()
+ }, { expect(3) })
+ finish(4)
+ }
+
+ @Test
+ fun testAsynchronousFatalExceptionFromSubscribe() = runTest {
+ rxFlowable(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ send(Unit)
+ }.publish()
+ .refCount()
+ .subscribe({
+ expect(2)
+ throw LinkageError()
+ }, { expectUnreached() })
+ finish(4)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/FlowableTest.kt b/reactive/kotlinx-coroutines-rx2/test/FlowableTest.kt
new file mode 100644
index 00000000..aebf9993
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/FlowableTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016-2018 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.reactive.*
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class FlowableTest : TestBase() {
+ @Test
+ fun testBasicSuccess() = runBlocking {
+ expect(1)
+ val observable = rxFlowable(currentDispatcher()) {
+ expect(4)
+ send("OK")
+ }
+ expect(2)
+ observable.subscribe { value ->
+ expect(5)
+ Assert.assertThat(value, IsEqual("OK"))
+ }
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicFailure() = runBlocking {
+ expect(1)
+ val observable = rxFlowable<String>(currentDispatcher()) {
+ expect(4)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ observable.subscribe({
+ expectUnreached()
+ }, { error ->
+ expect(5)
+ Assert.assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ Assert.assertThat(error.message, IsEqual("OK"))
+ })
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicUnsubscribe() = runBlocking {
+ expect(1)
+ val observable = rxFlowable<String>(currentDispatcher()) {
+ expect(4)
+ yield() // back to main, will get cancelled
+ expectUnreached()
+ }
+ expect(2)
+ val sub = observable.subscribe({
+ expectUnreached()
+ }, {
+ expectUnreached()
+ })
+ expect(3)
+ yield() // to started coroutine
+ expect(5)
+ sub.dispose() // will cancel coroutine
+ yield()
+ finish(6)
+ }
+
+ @Test
+ fun testNotifyOnceOnCancellation() = runTest {
+ expect(1)
+ val observable =
+ rxFlowable(currentDispatcher()) {
+ expect(5)
+ send("OK")
+ try {
+ delay(Long.MAX_VALUE)
+ } catch (e: CancellationException) {
+ expect(11)
+ }
+ }
+ .doOnNext {
+ expect(6)
+ assertEquals("OK", it)
+ }
+ .doOnCancel {
+ expect(10) // notified once!
+ }
+ expect(2)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(3)
+ observable.collect {
+ expect(8)
+ assertEquals("OK", it)
+ }
+ }
+ expect(4)
+ yield() // to observable code
+ expect(7)
+ yield() // to consuming coroutines
+ expect(9)
+ job.cancel()
+ job.join()
+ finish(12)
+ }
+
+ @Test
+ fun testFailingConsumer() = runTest {
+ val pub = rxFlowable(currentDispatcher()) {
+ repeat(3) {
+ expect(it + 1) // expect(1), expect(2) *should* be invoked
+ send(it)
+ }
+ }
+ try {
+ pub.collect {
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ finish(3)
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt
new file mode 100644
index 00000000..ca7c0ca5
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.runner.*
+import org.junit.runners.*
+import kotlin.coroutines.*
+
+@RunWith(Parameterized::class)
+class IntegrationTest(
+ private val ctx: Ctx,
+ private val delay: Boolean
+) : TestBase() {
+
+ enum class Ctx {
+ MAIN { override fun invoke(context: CoroutineContext): CoroutineContext = context.minusKey(Job) },
+ DEFAULT { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Default },
+ UNCONFINED { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Unconfined };
+
+ abstract operator fun invoke(context: CoroutineContext): CoroutineContext
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "ctx={0}, delay={1}")
+ @JvmStatic
+ fun params(): Collection<Array<Any>> = Ctx.values().flatMap { ctx ->
+ listOf(false, true).map { delay ->
+ arrayOf(ctx, delay)
+ }
+ }
+ }
+
+ @Test
+ fun testEmpty(): Unit = runBlocking {
+ val observable = rxObservable<String>(ctx(coroutineContext)) {
+ if (delay) delay(1)
+ // does not send anything
+ }
+ assertNSE { observable.awaitFirst() }
+ assertThat(observable.awaitFirstOrDefault("OK"), IsEqual("OK"))
+ assertThat(observable.awaitFirstOrNull(), IsNull())
+ assertThat(observable.awaitFirstOrElse { "ELSE" }, IsEqual("ELSE"))
+ assertNSE { observable.awaitLast() }
+ assertNSE { observable.awaitSingle() }
+ var cnt = 0
+ observable.collect {
+ cnt++
+ }
+ assertThat(cnt, IsEqual(0))
+ }
+
+ @Test
+ fun testSingle() = runBlocking {
+ val observable = rxObservable(ctx(coroutineContext)) {
+ if (delay) delay(1)
+ send("OK")
+ }
+ assertThat(observable.awaitFirst(), IsEqual("OK"))
+ assertThat(observable.awaitFirstOrDefault("OK"), IsEqual("OK"))
+ assertThat(observable.awaitFirstOrNull(), IsEqual("OK"))
+ assertThat(observable.awaitFirstOrElse { "ELSE" }, IsEqual("OK"))
+ assertThat(observable.awaitLast(), IsEqual("OK"))
+ assertThat(observable.awaitSingle(), IsEqual("OK"))
+ var cnt = 0
+ observable.collect {
+ assertThat(it, IsEqual("OK"))
+ cnt++
+ }
+ assertThat(cnt, IsEqual(1))
+ }
+
+ @Test
+ fun testNumbers() = runBlocking<Unit> {
+ val n = 100 * stressTestMultiplier
+ val observable = rxObservable(ctx(coroutineContext)) {
+ for (i in 1..n) {
+ send(i)
+ if (delay) delay(1)
+ }
+ }
+ assertThat(observable.awaitFirst(), IsEqual(1))
+ assertThat(observable.awaitFirstOrDefault(0), IsEqual(1))
+ assertThat(observable.awaitFirstOrNull(), IsEqual(1))
+ assertThat(observable.awaitFirstOrElse { 0 }, IsEqual(1))
+ assertThat(observable.awaitLast(), IsEqual(n))
+ assertIAE { observable.awaitSingle() }
+ checkNumbers(n, observable)
+ val channel = observable.openSubscription()
+ checkNumbers(n, channel.asObservable(ctx(coroutineContext)))
+ channel.cancel()
+ }
+
+ @Test
+ fun testCancelWithoutValue() = runTest {
+ val job = launch(Job(), start = CoroutineStart.UNDISPATCHED) {
+ rxObservable<String> {
+ hang { }
+ }.awaitFirst()
+ }
+
+ job.cancel()
+ job.join()
+ }
+
+ @Test
+ fun testEmptySingle() = runTest(unhandled = listOf({e -> e is NoSuchElementException})) {
+ expect(1)
+ val job = launch(Job(), start = CoroutineStart.UNDISPATCHED) {
+ rxObservable<String> {
+ yield()
+ expect(2)
+ // Nothing to emit
+ }.awaitFirst()
+ }
+
+ job.join()
+ finish(3)
+ }
+
+ private suspend fun checkNumbers(n: Int, observable: Observable<Int>) {
+ var last = 0
+ observable.collect {
+ assertThat(it, IsEqual(++last))
+ }
+ assertThat(last, IsEqual(n))
+ }
+
+
+ private inline fun assertIAE(block: () -> Unit) {
+ try {
+ block()
+ expectUnreached()
+ } catch (e: Throwable) {
+ assertThat(e, IsInstanceOf(IllegalArgumentException::class.java))
+ }
+ }
+
+ private inline fun assertNSE(block: () -> Unit) {
+ try {
+ block()
+ expectUnreached()
+ } catch (e: Throwable) {
+ assertThat(e, IsInstanceOf(NoSuchElementException::class.java))
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/IterableFlowAsFlowableTckTest.kt b/reactive/kotlinx-coroutines-rx2/test/IterableFlowAsFlowableTckTest.kt
new file mode 100644
index 00000000..cc22e33a
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/IterableFlowAsFlowableTckTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2019 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 kotlinx.coroutines.flow.*
+import org.junit.*
+import org.reactivestreams.*
+import org.reactivestreams.tck.*
+
+class IterableFlowAsFlowableTckTest : PublisherVerification<Long>(TestEnvironment()) {
+
+ private fun generate(num: Long): Array<Long> {
+ return Array(if (num >= Integer.MAX_VALUE) 1000000 else num.toInt()) { it.toLong() }
+ }
+
+ override fun createPublisher(elements: Long): Flowable<Long> {
+ return generate(elements).asIterable().asFlow().asFlowable()
+ }
+
+ override fun createFailedPublisher(): Publisher<Long>? = null
+
+ @Ignore
+ override fun required_spec309_requestZeroMustSignalIllegalArgumentException() {
+ }
+
+ @Ignore
+ override fun required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() {
+ }
+
+ @Ignore
+ override fun required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() {
+ //
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt b/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt
new file mode 100644
index 00000000..dcd66638
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2016-2018 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 io.reactivex.functions.*
+import io.reactivex.internal.functions.Functions.*
+import kotlinx.coroutines.*
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+import java.util.concurrent.*
+import java.util.concurrent.CancellationException
+
+class MaybeTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("RxComputationThreadPool-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+ }
+
+ @Test
+ fun testBasicSuccess() = runBlocking {
+ expect(1)
+ val maybe = rxMaybe(currentDispatcher()) {
+ expect(4)
+ "OK"
+ }
+ expect(2)
+ maybe.subscribe { value ->
+ expect(5)
+ assertThat(value, IsEqual("OK"))
+ }
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicEmpty() = runBlocking {
+ expect(1)
+ val maybe = rxMaybe(currentDispatcher()) {
+ expect(4)
+ null
+ }
+ expect(2)
+ maybe.subscribe (emptyConsumer(), ON_ERROR_MISSING, Action {
+ expect(5)
+ })
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicFailure() = runBlocking {
+ expect(1)
+ val maybe = rxMaybe(currentDispatcher()) {
+ expect(4)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ maybe.subscribe({
+ expectUnreached()
+ }, { error ->
+ expect(5)
+ Assert.assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ Assert.assertThat(error.message, IsEqual("OK"))
+ })
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+
+ @Test
+ fun testBasicUnsubscribe() = runBlocking {
+ expect(1)
+ val maybe = rxMaybe(currentDispatcher()) {
+ expect(4)
+ yield() // back to main, will get cancelled
+ expectUnreached()
+ }
+ expect(2)
+ // nothing is called on a disposed rx2 maybe
+ val sub = maybe.subscribe({
+ expectUnreached()
+ }, {
+ expectUnreached()
+ })
+ expect(3)
+ yield() // to started coroutine
+ expect(5)
+ sub.dispose() // will cancel coroutine
+ yield()
+ finish(6)
+ }
+
+ @Test
+ fun testMaybeNoWait() {
+ val maybe = rxMaybe {
+ "OK"
+ }
+
+ checkMaybeValue(maybe) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testMaybeAwait() = runBlocking {
+ assertEquals("OK", Maybe.just("O").await() + "K")
+ }
+
+ @Test
+ fun testMaybeAwaitForNull() = runBlocking {
+ assertNull(Maybe.empty<String>().await())
+ }
+
+ @Test
+ fun testMaybeEmitAndAwait() {
+ val maybe = rxMaybe {
+ Maybe.just("O").await() + "K"
+ }
+
+ checkMaybeValue(maybe) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testMaybeWithDelay() {
+ val maybe = rxMaybe {
+ Observable.timer(50, TimeUnit.MILLISECONDS).map { "O" }.awaitSingle() + "K"
+ }
+
+ checkMaybeValue(maybe) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testMaybeException() {
+ val maybe = rxMaybe {
+ Observable.just("O", "K").awaitSingle() + "K"
+ }
+
+ checkErroneous(maybe) {
+ assert(it is IllegalArgumentException)
+ }
+ }
+
+ @Test
+ fun testAwaitFirst() {
+ val maybe = rxMaybe {
+ Observable.just("O", "#").awaitFirst() + "K"
+ }
+
+ checkMaybeValue(maybe) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitLast() {
+ val maybe = rxMaybe {
+ Observable.just("#", "O").awaitLast() + "K"
+ }
+
+ checkMaybeValue(maybe) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromObservable() {
+ val maybe = rxMaybe {
+ try {
+ Observable.error<String>(RuntimeException("O")).awaitFirst()
+ } catch (e: RuntimeException) {
+ Observable.just(e.message!!).awaitLast() + "K"
+ }
+ }
+
+ checkMaybeValue(maybe) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromCoroutine() {
+ val maybe = rxMaybe<String> {
+ throw IllegalStateException(Observable.just("O").awaitSingle() + "K")
+ }
+
+ checkErroneous(maybe) {
+ assert(it is IllegalStateException)
+ assertEquals("OK", it.message)
+ }
+ }
+
+ @Test
+ fun testCancelledConsumer() = runTest {
+ expect(1)
+ val maybe = rxMaybe<Int>(currentDispatcher()) {
+ expect(4)
+ try {
+ delay(Long.MAX_VALUE)
+ } catch (e: CancellationException) {
+ expect(6)
+ }
+ 42
+ }
+ expect(2)
+ val timeout = withTimeoutOrNull(100) {
+ expect(3)
+ maybe.collect {
+ expectUnreached()
+ }
+ expectUnreached()
+ }
+ assertNull(timeout)
+ expect(5)
+ yield() // must cancel code inside maybe!!!
+ finish(7)
+ }
+
+ @Test
+ fun testSuppressedException() = runTest {
+ val maybe = rxMaybe(currentDispatcher()) {
+ launch(start = CoroutineStart.ATOMIC) {
+ throw TestException() // child coroutine fails
+ }
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException2() // but parent throws another exception while cleaning up
+ }
+ }
+ try {
+ maybe.await()
+ expectUnreached()
+ } catch (e: TestException) {
+ assertTrue(e.suppressed[0] is TestException2)
+ }
+ }
+
+ @Test
+ fun testUnhandledException() = runTest {
+ expect(1)
+ var disposable: Disposable? = null
+ val eh = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is TestException)
+ expect(5)
+ }
+ val maybe = rxMaybe(currentDispatcher() + eh) {
+ expect(4)
+ disposable!!.dispose() // cancel our own subscription, so that delay will get cancelled
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException() // would not be able to handle it since mono is disposed
+ }
+ }
+ maybe.subscribe(object : MaybeObserver<Unit> {
+ override fun onSubscribe(d: Disposable) {
+ expect(2)
+ disposable = d
+ }
+ override fun onComplete() { expectUnreached() }
+ override fun onSuccess(t: Unit) { expectUnreached() }
+ override fun onError(t: Throwable) { expectUnreached() }
+ })
+ expect(3)
+ yield() // run coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testFatalExceptionInSubscribe() = runTest {
+ GlobalScope.rxMaybe(Dispatchers.Unconfined + CoroutineExceptionHandler{ _, e -> assertTrue(e is LinkageError); expect(2)}) {
+ expect(1)
+ 42
+ }.subscribe({ throw LinkageError() })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionInSingle() = runTest {
+ GlobalScope.rxMaybe(Dispatchers.Unconfined) {
+ throw LinkageError()
+ }.subscribe({ expectUnreached() }, { expect(1); assertTrue(it is LinkageError) })
+ finish(2)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableCompletionStressTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableCompletionStressTest.kt
new file mode 100644
index 00000000..30266e3e
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableCompletionStressTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2018 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 org.junit.*
+import java.util.*
+import kotlin.coroutines.*
+
+class ObservableCompletionStressTest : TestBase() {
+ private val N_REPEATS = 10_000 * stressTestMultiplier
+
+ private fun CoroutineScope.range(context: CoroutineContext, start: Int, count: Int) = rxObservable(context) {
+ for (x in start until start + count) send(x)
+ }
+
+ @Test
+ fun testCompletion() {
+ val rnd = Random()
+ repeat(N_REPEATS) {
+ val count = rnd.nextInt(5)
+ runBlocking {
+ withTimeout(5000) {
+ var received = 0
+ range(Dispatchers.Default, 1, count).collect { x ->
+ received++
+ if (x != received) error("$x != $received")
+ }
+ if (received != count) error("$received != $count")
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt
new file mode 100644
index 00000000..6d247cfa
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016-2019 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 org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class ObservableExceptionHandlingTest : TestBase() {
+
+ @Before
+ fun setup() {
+ ignoreLostThreads("RxComputationThreadPool-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+ }
+
+ private inline fun <reified T : Throwable> ceh(expect: Int) = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is T)
+ expect(expect)
+ }
+
+ private fun cehUnreached() = CoroutineExceptionHandler { _, _ -> expectUnreached() }
+
+ @Test
+ fun testException() = runTest {
+ rxObservable<Int>(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ throw TestException()
+ }.subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Reported to onError
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalException() = runTest {
+ rxObservable<Int>(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ throw LinkageError()
+ }.subscribe({
+ expectUnreached()
+ }, {
+ expect(2)
+ })
+ finish(4)
+ }
+
+ @Test
+ fun testExceptionAsynchronous() = runTest {
+ rxObservable<Int>(Dispatchers.Unconfined) {
+ expect(1)
+ throw TestException()
+ }.publish()
+ .refCount()
+ .subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Reported to onError
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionAsynchronous() = runTest {
+ rxObservable<Int>(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ throw LinkageError()
+ }.publish()
+ .refCount()
+ .subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Fatal exception is not reported in onError
+ })
+ finish(4)
+ }
+
+ @Test
+ fun testFatalExceptionFromSubscribe() = runTest {
+ rxObservable(Dispatchers.Unconfined + ceh<LinkageError>(4)) {
+ expect(1)
+ send(Unit)
+ }.subscribe({
+ expect(2)
+ throw LinkageError()
+ }, { expect(3) }) // Unreached because fatal errors are rethrown
+ finish(5)
+ }
+
+ @Test
+ fun testExceptionFromSubscribe() = runTest {
+ rxObservable(Dispatchers.Unconfined) {
+ expect(1)
+ send(Unit)
+ }.subscribe({
+ expect(2)
+ throw TestException()
+ }, { expect(3) }) // not reported to onError because came from the subscribe itself
+ finish(4)
+ }
+
+ @Test
+ fun testAsynchronousExceptionFromSubscribe() = runTest {
+ rxObservable(Dispatchers.Unconfined) {
+ expect(1)
+ send(Unit)
+ }.publish()
+ .refCount()
+ .subscribe({
+ expect(2)
+ throw RuntimeException()
+ }, { expect(3) })
+ finish(4)
+ }
+
+ @Test
+ fun testAsynchronousFatalExceptionFromSubscribe() = runTest {
+ rxObservable(Dispatchers.Unconfined + ceh<LinkageError>(4)) {
+ expect(1)
+ send(Unit)
+ }.publish()
+ .refCount()
+ .subscribe({
+ expect(2)
+ throw LinkageError()
+ }, { expect(3) })
+ finish(5)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt
new file mode 100644
index 00000000..75f79de5
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Assert.*
+import java.io.*
+import kotlin.experimental.*
+
+/**
+ * Test emitting multiple values with [rxObservable].
+ */
+class ObservableMultiTest : TestBase() {
+ @Test
+ fun testNumbers() {
+ val n = 100 * stressTestMultiplier
+ val observable = GlobalScope.rxObservable {
+ repeat(n) { send(it) }
+ }
+ checkSingleValue(observable.toList()) { list ->
+ assertEquals((0 until n).toList(), list)
+ }
+ }
+
+
+ @Test
+ fun testConcurrentStress() {
+ val n = 10_000 * stressTestMultiplier
+ val observable = GlobalScope.rxObservable {
+ newCoroutineContext(coroutineContext)
+ // concurrent emitters (many coroutines)
+ val jobs = List(n) {
+ // launch
+ launch {
+ val i = it
+ send(i)
+ }
+ }
+ jobs.forEach { it.join() }
+ }
+ checkSingleValue(observable.toList()) { list ->
+ assertEquals(n, list.size)
+ assertEquals((0 until n).toList(), list.sorted())
+ }
+ }
+
+ @Test
+ fun testIteratorResendUnconfined() {
+ val n = 10_000 * stressTestMultiplier
+ val observable = GlobalScope.rxObservable(Dispatchers.Unconfined) {
+ Observable.range(0, n).collect { send(it) }
+ }
+ checkSingleValue(observable.toList()) { list ->
+ assertEquals((0 until n).toList(), list)
+ }
+ }
+
+ @Test
+ fun testIteratorResendPool() {
+ val n = 10_000 * stressTestMultiplier
+ val observable = GlobalScope.rxObservable {
+ Observable.range(0, n).collect { send(it) }
+ }
+ checkSingleValue(observable.toList()) { list ->
+ assertEquals((0 until n).toList(), list)
+ }
+ }
+
+ @Test
+ fun testSendAndCrash() {
+ val observable = GlobalScope.rxObservable {
+ send("O")
+ throw IOException("K")
+ }
+ val single = rxSingle {
+ var result = ""
+ try {
+ observable.consumeEach { result += it }
+ } catch(e: IOException) {
+ result += e.message
+ }
+ result
+ }
+ checkSingleValue(single) {
+ assertEquals("OK", it)
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt
new file mode 100644
index 00000000..6b5d7451
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2016-2018 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 kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Assert.*
+import java.util.concurrent.*
+
+class ObservableSingleTest {
+ @Test
+ fun testSingleNoWait() {
+ val observable = GlobalScope.rxObservable {
+ send("OK")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleAwait() = runBlocking {
+ assertEquals("OK", Observable.just("O").awaitSingle() + "K")
+ }
+
+ @Test
+ fun testSingleEmitAndAwait() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.just("O").awaitSingle() + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleWithDelay() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.timer(50, TimeUnit.MILLISECONDS).map { "O" }.awaitSingle() + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleException() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.just("O", "K").awaitSingle() + "K")
+ }
+
+ checkErroneous(observable) {
+ assertTrue(it is IllegalArgumentException)
+ }
+ }
+
+ @Test
+ fun testAwaitFirst() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.just("O", "#").awaitFirst() + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrDefault() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.empty<String>().awaitFirstOrDefault("O") + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrDefaultWithValues() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.just("O", "#").awaitFirstOrDefault("!") + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrNull() {
+ val observable = GlobalScope.rxObservable<String> {
+ send(Observable.empty<String>().awaitFirstOrNull() ?: "OK")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrNullWithValues() {
+ val observable = GlobalScope.rxObservable {
+ send((Observable.just("O", "#").awaitFirstOrNull() ?: "!") + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrElse() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.empty<String>().awaitFirstOrElse { "O" } + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitFirstOrElseWithValues() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.just("O", "#").awaitFirstOrElse { "!" } + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitLast() {
+ val observable = GlobalScope.rxObservable {
+ send(Observable.just("#", "O").awaitLast() + "K")
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromObservable() {
+ val observable = GlobalScope.rxObservable {
+ try {
+ send(Observable.error<String>(RuntimeException("O")).awaitFirst())
+ } catch (e: RuntimeException) {
+ send(Observable.just(e.message!!).awaitLast() + "K")
+ }
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromCoroutine() {
+ val observable = GlobalScope.rxObservable<String> {
+ error(Observable.just("O").awaitSingle() + "K")
+ }
+
+ checkErroneous(observable) {
+ assertTrue(it is IllegalStateException)
+ assertEquals("OK", it.message)
+ }
+ }
+
+ @Test
+ fun testObservableIteration() {
+ val observable = GlobalScope.rxObservable {
+ var result = ""
+ Observable.just("O", "K").collect { result += it }
+ send(result)
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testObservableIterationFailure() {
+ val observable = GlobalScope.rxObservable {
+ try {
+ Observable.error<String>(RuntimeException("OK")).collect { fail("Should not be here") }
+ send("Fail")
+ } catch (e: RuntimeException) {
+ send(e.message!!)
+ }
+ }
+
+ checkSingleValue(observable) {
+ assertEquals("OK", it)
+ }
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt
new file mode 100644
index 00000000..28eb8074
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-2018 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.selects.*
+import org.junit.*
+import org.junit.Assert.*
+
+class ObservableSubscriptionSelectTest : TestBase() {
+ @Test
+ fun testSelect() = runTest {
+ // source with n ints
+ val n = 1000 * stressTestMultiplier
+ val source = rxObservable { repeat(n) { send(it) } }
+ var a = 0
+ var b = 0
+ // open two subs
+ val channelA = source.openSubscription()
+ 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
+ }
+ channelB.onReceiveOrNull {
+ if (it != null) assertEquals(b++, it)
+ if (it == null) 0 else 2
+ }
+ }
+ when (done) {
+ 0 -> break@loop
+ 1 -> {
+ val r = channelB.receiveOrNull()
+ if (r != null) assertEquals(b++, r)
+ }
+ 2 -> {
+ val r = channelA.receiveOrNull()
+ if (r != null) assertEquals(a++, r)
+ }
+ }
+ }
+ channelA.cancel()
+ channelB.cancel()
+ // 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/ObservableTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableTest.kt
new file mode 100644
index 00000000..c71ef566
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2016-2018 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 org.hamcrest.core.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class ObservableTest : TestBase() {
+ @Test
+ fun testBasicSuccess() = runBlocking {
+ expect(1)
+ val observable = rxObservable(currentDispatcher()) {
+ expect(4)
+ send("OK")
+ }
+ expect(2)
+ observable.subscribe { value ->
+ expect(5)
+ Assert.assertThat(value, IsEqual("OK"))
+ }
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicFailure() = runBlocking {
+ expect(1)
+ val observable = rxObservable<String>(currentDispatcher()) {
+ expect(4)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ observable.subscribe({
+ expectUnreached()
+ }, { error ->
+ expect(5)
+ Assert.assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ Assert.assertThat(error.message, IsEqual("OK"))
+ })
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicUnsubscribe() = runBlocking {
+ expect(1)
+ val observable = rxObservable<String>(currentDispatcher()) {
+ expect(4)
+ yield() // back to main, will get cancelled
+ expectUnreached()
+ }
+ expect(2)
+ val sub = observable.subscribe({
+ expectUnreached()
+ }, {
+ expectUnreached()
+ })
+ expect(3)
+ yield() // to started coroutine
+ expect(5)
+ sub.dispose() // will cancel coroutine
+ yield()
+ finish(6)
+ }
+
+ @Test
+ fun testNotifyOnceOnCancellation() = runTest {
+ expect(1)
+ val observable =
+ rxObservable(currentDispatcher()) {
+ expect(5)
+ send("OK")
+ try {
+ delay(Long.MAX_VALUE)
+ } catch (e: CancellationException) {
+ expect(11)
+ }
+ }
+ .doOnNext {
+ expect(6)
+ assertEquals("OK", it)
+ }
+ .doOnDispose {
+ expect(10) // notified once!
+ }
+ expect(2)
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(3)
+ observable.collect {
+ expect(8)
+ assertEquals("OK", it)
+ }
+ }
+ expect(4)
+ yield() // to observable code
+ expect(7)
+ yield() // to consuming coroutines
+ expect(9)
+ job.cancel()
+ job.join()
+ finish(12)
+ }
+
+ @Test
+ fun testFailingConsumer() = runTest {
+ expect(1)
+ val pub = rxObservable(currentDispatcher()) {
+ expect(2)
+ send("OK")
+ try {
+ delay(Long.MAX_VALUE)
+ } catch (e: CancellationException) {
+ finish(5)
+ }
+ }
+ try {
+ pub.collect {
+ expect(3)
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ expect(4)
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/SchedulerTest.kt b/reactive/kotlinx-coroutines-rx2/test/SchedulerTest.kt
new file mode 100644
index 00000000..ca98b45d
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/SchedulerTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx2
+
+import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.*
+import org.hamcrest.core.IsEqual
+import org.hamcrest.core.IsNot
+import org.junit.Assert.assertThat
+import org.junit.Before
+import org.junit.Test
+
+class SchedulerTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("RxCachedThreadScheduler-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+ }
+
+ @Test
+ fun testIoScheduler(): Unit = runBlocking {
+ expect(1)
+ val mainThread = Thread.currentThread()
+ withContext(Schedulers.io().asCoroutineDispatcher()) {
+ val t1 = Thread.currentThread()
+ assertThat(t1, IsNot(IsEqual(mainThread)))
+ expect(2)
+ delay(100)
+ val t2 = Thread.currentThread()
+ assertThat(t2, IsNot(IsEqual(mainThread)))
+ expect(3)
+ }
+ finish(4)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt b/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt
new file mode 100644
index 00000000..fce77234
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2016-2018 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 io.reactivex.functions.*
+import kotlinx.coroutines.*
+import org.hamcrest.core.*
+import org.junit.*
+import org.junit.Assert.*
+import java.util.concurrent.*
+
+class SingleTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("RxComputationThreadPool-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+ }
+
+ @Test
+ fun testBasicSuccess() = runBlocking {
+ expect(1)
+ val single = rxSingle(currentDispatcher()) {
+ expect(4)
+ "OK"
+ }
+ expect(2)
+ single.subscribe { value ->
+ expect(5)
+ assertThat(value, IsEqual("OK"))
+ }
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+ @Test
+ fun testBasicFailure() = runBlocking {
+ expect(1)
+ val single = rxSingle(currentDispatcher()) {
+ expect(4)
+ throw RuntimeException("OK")
+ }
+ expect(2)
+ single.subscribe({
+ expectUnreached()
+ }, { error ->
+ expect(5)
+ assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ assertThat(error.message, IsEqual("OK"))
+ })
+ expect(3)
+ yield() // to started coroutine
+ finish(6)
+ }
+
+
+ @Test
+ fun testBasicUnsubscribe() = runBlocking {
+ expect(1)
+ val single = rxSingle(currentDispatcher()) {
+ expect(4)
+ yield() // back to main, will get cancelled
+ expectUnreached()
+
+ }
+ expect(2)
+ // nothing is called on a disposed rx2 single
+ val sub = single.subscribe({
+ expectUnreached()
+ }, {
+ expectUnreached()
+ })
+ expect(3)
+ yield() // to started coroutine
+ expect(5)
+ sub.dispose() // will cancel coroutine
+ yield()
+ finish(6)
+ }
+
+ @Test
+ fun testSingleNoWait() {
+ val single = rxSingle {
+ "OK"
+ }
+
+ checkSingleValue(single) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleAwait() = runBlocking {
+ assertEquals("OK", Single.just("O").await() + "K")
+ }
+
+ @Test
+ fun testSingleEmitAndAwait() {
+ val single = rxSingle {
+ Single.just("O").await() + "K"
+ }
+
+ checkSingleValue(single) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleWithDelay() {
+ val single = rxSingle {
+ Observable.timer(50, TimeUnit.MILLISECONDS).map { "O" }.awaitSingle() + "K"
+ }
+
+ checkSingleValue(single) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testSingleException() {
+ val single = rxSingle {
+ Observable.just("O", "K").awaitSingle() + "K"
+ }
+
+ checkErroneous(single) {
+ assert(it is IllegalArgumentException)
+ }
+ }
+
+ @Test
+ fun testAwaitFirst() {
+ val single = rxSingle {
+ Observable.just("O", "#").awaitFirst() + "K"
+ }
+
+ checkSingleValue(single) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testAwaitLast() {
+ val single = rxSingle {
+ Observable.just("#", "O").awaitLast() + "K"
+ }
+
+ checkSingleValue(single) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromObservable() {
+ val single = rxSingle {
+ try {
+ Observable.error<String>(RuntimeException("O")).awaitFirst()
+ } catch (e: RuntimeException) {
+ Observable.just(e.message!!).awaitLast() + "K"
+ }
+ }
+
+ checkSingleValue(single) {
+ assertEquals("OK", it)
+ }
+ }
+
+ @Test
+ fun testExceptionFromCoroutine() {
+ val single = rxSingle<String> {
+ throw IllegalStateException(Observable.just("O").awaitSingle() + "K")
+ }
+
+ checkErroneous(single) {
+ assert(it is IllegalStateException)
+ assertEquals("OK", it.message)
+ }
+ }
+
+ @Test
+ fun testSuppressedException() = runTest {
+ val single = rxSingle(currentDispatcher()) {
+ launch(start = CoroutineStart.ATOMIC) {
+ throw TestException() // child coroutine fails
+ }
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException2() // but parent throws another exception while cleaning up
+ }
+ }
+ try {
+ single.await()
+ expectUnreached()
+ } catch (e: TestException) {
+ assertTrue(e.suppressed[0] is TestException2)
+ }
+ }
+
+ @Test
+ fun testFatalExceptionInSubscribe() = runTest {
+ GlobalScope.rxSingle(Dispatchers.Unconfined + CoroutineExceptionHandler { _, e -> assertTrue(e is LinkageError); expect(2) }) {
+ expect(1)
+ 42
+ }.subscribe(Consumer {
+ throw LinkageError()
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionInSingle() = runTest {
+ GlobalScope.rxSingle(Dispatchers.Unconfined) {
+ throw LinkageError()
+ }.subscribe({ _, e -> assertTrue(e is LinkageError); expect(1) })
+
+ finish(2)
+ }
+
+ @Test
+ fun testUnhandledException() = runTest {
+ expect(1)
+ var disposable: Disposable? = null
+ val eh = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is TestException)
+ expect(5)
+ }
+ val single = rxSingle(currentDispatcher() + eh) {
+ expect(4)
+ disposable!!.dispose() // cancel our own subscription, so that delay will get cancelled
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ throw TestException() // would not be able to handle it since mono is disposed
+ }
+ }
+ single.subscribe(object : SingleObserver<Unit> {
+ override fun onSubscribe(d: Disposable) {
+ expect(2)
+ disposable = d
+ }
+ override fun onSuccess(t: Unit) { expectUnreached() }
+ override fun onError(t: Throwable) { expectUnreached() }
+ })
+ expect(3)
+ yield() // run coroutine
+ finish(6)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt
new file mode 100644
index 00000000..f3bc344d
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic01
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlin.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ // create a channel that produces numbers from 1 to 3 with 200ms delays between them
+ val source = produce<Int> {
+ println("Begin") // mark the beginning of this coroutine in output
+ for (x in 1..3) {
+ delay(200) // wait for 200ms
+ send(x) // send number x to the channel
+ }
+ }
+ // print elements from the source
+ println("Elements:")
+ source.consumeEach { // consume elements from it
+ println(it)
+ }
+ // print elements from the source AGAIN
+ println("Again:")
+ source.consumeEach { // consume elements from it
+ println(it)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt
new file mode 100644
index 00000000..0e0ff2e5
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic02
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ // create a publisher that produces numbers from 1 to 3 with 200ms delays between them
+ val source = publish<Int> {
+ // ^^^^^^^ <--- Difference from the previous examples is here
+ println("Begin") // mark the beginning of this coroutine in output
+ for (x in 1..3) {
+ delay(200) // wait for 200ms
+ send(x) // send number x to the channel
+ }
+ }
+ // print elements from the source
+ println("Elements:")
+ source.collect { // collect elements from it
+ println(it)
+ }
+ // print elements from the source AGAIN
+ println("Again:")
+ source.collect { // collect elements from it
+ println(it)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt
new file mode 100644
index 00000000..b84fc08f
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic03
+
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.reactive.*
+
+fun main() = runBlocking<Unit> {
+ val source = Flowable.range(1, 5) // a range of five numbers
+ .doOnSubscribe { println("OnSubscribe") } // provide some insight
+ .doOnComplete { println("OnComplete") } // ...
+ .doFinally { println("Finally") } // ... into what's going on
+ var cnt = 0
+ source.openSubscription().consume { // open channel to the source
+ for (x in this) { // iterate over the channel to receive elements from it
+ println(x)
+ if (++cnt >= 3) break // break when 3 elements are printed
+ }
+ // Note: `consume` cancels the channel when this block of code is complete
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt
new file mode 100644
index 00000000..a08c41fc
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic04
+
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ val source = Flowable.range(1, 5) // a range of five numbers
+ .doOnSubscribe { println("OnSubscribe") } // provide some insight
+ .doOnComplete { println("OnComplete") } // ...
+ .doFinally { println("Finally") } // ... into what's going on
+ // collect the source fully
+ source.collect { println(it) }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt
new file mode 100644
index 00000000..e6428b92
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic05
+
+import io.reactivex.schedulers.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.rx2.*
+import kotlin.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ // coroutine -- fast producer of elements in the context of the main thread
+ val source = rxFlowable {
+ for (x in 1..3) {
+ send(x) // this is a suspending function
+ println("Sent $x") // print after successfully sent item
+ }
+ }
+ // subscribe on another thread with a slow subscriber using Rx
+ source
+ .observeOn(Schedulers.io(), false, 1) // specify buffer size of 1 item
+ .doOnComplete { println("Complete") }
+ .subscribe { x ->
+ Thread.sleep(500) // 500ms to process each item
+ println("Processed $x")
+ }
+ delay(2000) // suspend the main thread for a few seconds
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt
new file mode 100644
index 00000000..1f3747f4
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic06
+
+import io.reactivex.subjects.BehaviorSubject
+
+fun main() {
+ val subject = BehaviorSubject.create<String>()
+ subject.onNext("one")
+ subject.onNext("two") // updates the state of BehaviorSubject, "one" value is lost
+ // now subscribe to this subject and print everything
+ subject.subscribe(System.out::println)
+ subject.onNext("three")
+ subject.onNext("four")
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt
new file mode 100644
index 00000000..b4cc9fc9
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic07
+
+import io.reactivex.subjects.BehaviorSubject
+import kotlinx.coroutines.*
+import kotlinx.coroutines.rx2.collect
+
+fun main() = runBlocking<Unit> {
+ val subject = BehaviorSubject.create<String>()
+ subject.onNext("one")
+ subject.onNext("two")
+ // now launch a coroutine to print everything
+ GlobalScope.launch(Dispatchers.Unconfined) { // launch coroutine in unconfined context
+ subject.collect { println(it) }
+ }
+ subject.onNext("three")
+ subject.onNext("four")
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt
new file mode 100644
index 00000000..8e17ac9c
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic08
+
+import io.reactivex.subjects.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.rx2.*
+import kotlin.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ val subject = BehaviorSubject.create<String>()
+ subject.onNext("one")
+ subject.onNext("two")
+ // now launch a coroutine to print the most recent update
+ launch { // use the context of the main thread for a coroutine
+ subject.collect { println(it) }
+ }
+ subject.onNext("three")
+ subject.onNext("four")
+ yield() // yield the main thread to the launched coroutine <--- HERE
+ subject.onComplete() // now complete the subject's sequence to cancel the consumer, too
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt
new file mode 100644
index 00000000..738c4aba
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.basic09
+
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ val broadcast = ConflatedBroadcastChannel<String>()
+ broadcast.offer("one")
+ broadcast.offer("two")
+ // now launch a coroutine to print the most recent update
+ launch { // use the context of the main thread for a coroutine
+ broadcast.consumeEach { println(it) }
+ }
+ broadcast.offer("three")
+ broadcast.offer("four")
+ yield() // yield the main thread to the launched coroutine
+ broadcast.close() // now close the broadcast channel to cancel the consumer, too
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt
new file mode 100644
index 00000000..b12e92ae
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.context01
+
+import io.reactivex.*
+import io.reactivex.functions.BiFunction
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+
+fun rangeWithIntervalRx(scheduler: Scheduler, time: Long, start: Int, count: Int): Flowable<Int> =
+ Flowable.zip(
+ Flowable.range(start, count),
+ Flowable.interval(time, TimeUnit.MILLISECONDS, scheduler),
+ BiFunction { x, _ -> x })
+
+fun main() {
+ rangeWithIntervalRx(Schedulers.computation(), 100, 1, 3)
+ .subscribe { println("$it on thread ${Thread.currentThread().name}") }
+ Thread.sleep(1000)
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt
new file mode 100644
index 00000000..b87849a5
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.context02
+
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.CoroutineContext
+
+fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
+ for (x in start until start + count) {
+ delay(time) // wait before sending each number
+ send(x)
+ }
+}
+
+fun main() {
+ Flowable.fromPublisher(rangeWithInterval(Dispatchers.Default, 100, 1, 3))
+ .subscribe { println("$it on thread ${Thread.currentThread().name}") }
+ Thread.sleep(1000)
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt
new file mode 100644
index 00000000..1a214ce3
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.context03
+
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import io.reactivex.schedulers.Schedulers
+import kotlin.coroutines.CoroutineContext
+
+fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
+ for (x in start until start + count) {
+ delay(time) // wait before sending each number
+ send(x)
+ }
+}
+
+fun main() {
+ Flowable.fromPublisher(rangeWithInterval(Dispatchers.Default, 100, 1, 3))
+ .observeOn(Schedulers.computation()) // <-- THIS LINE IS ADDED
+ .subscribe { println("$it on thread ${Thread.currentThread().name}") }
+ Thread.sleep(1000)
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt
new file mode 100644
index 00000000..3c5d3fb5
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.context04
+
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import io.reactivex.functions.BiFunction
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+
+fun rangeWithIntervalRx(scheduler: Scheduler, time: Long, start: Int, count: Int): Flowable<Int> =
+ Flowable.zip(
+ Flowable.range(start, count),
+ Flowable.interval(time, TimeUnit.MILLISECONDS, scheduler),
+ BiFunction { x, _ -> x })
+
+fun main() = runBlocking<Unit> {
+ rangeWithIntervalRx(Schedulers.computation(), 100, 1, 3)
+ .collect { println("$it on thread ${Thread.currentThread().name}") }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt
new file mode 100644
index 00000000..61b54b2b
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.context05
+
+import io.reactivex.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import io.reactivex.functions.BiFunction
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+
+fun rangeWithIntervalRx(scheduler: Scheduler, time: Long, start: Int, count: Int): Flowable<Int> =
+ Flowable.zip(
+ Flowable.range(start, count),
+ Flowable.interval(time, TimeUnit.MILLISECONDS, scheduler),
+ BiFunction { x, _ -> x })
+
+fun main() = runBlocking<Unit> {
+ val job = launch(Dispatchers.Unconfined) { // launch a new coroutine in Unconfined context (without its own thread pool)
+ rangeWithIntervalRx(Schedulers.computation(), 100, 1, 3)
+ .collect { println("$it on thread ${Thread.currentThread().name}") }
+ }
+ job.join() // wait for our coroutine to complete
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt
new file mode 100644
index 00000000..8268ef27
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.operators01
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlin.coroutines.CoroutineContext
+
+fun CoroutineScope.range(context: CoroutineContext, start: Int, count: Int) = publish<Int>(context) {
+ for (x in start until start + count) send(x)
+}
+
+fun main() = runBlocking<Unit> {
+ // Range inherits parent job from runBlocking, but overrides dispatcher with Dispatchers.Default
+ range(Dispatchers.Default, 1, 5).collect { println(it) }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt
new file mode 100644
index 00000000..5f07ba49
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.operators02
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.reactivestreams.*
+import kotlin.coroutines.*
+
+fun <T, R> Publisher<T>.fusedFilterMap(
+ context: CoroutineContext, // the context to execute this coroutine in
+ predicate: (T) -> Boolean, // the filter predicate
+ mapper: (T) -> R // the mapper function
+) = publish<R>(context) {
+ collect { // collect the source stream
+ if (predicate(it)) // filter part
+ send(mapper(it)) // map part
+ }
+}
+
+fun CoroutineScope.range(start: Int, count: Int) = publish<Int> {
+ for (x in start until start + count) send(x)
+}
+
+fun main() = runBlocking<Unit> {
+ range(1, 5)
+ .fusedFilterMap(Dispatchers.Unconfined, { it % 2 == 0}, { "$it is even" })
+ .collect { println(it) } // print all the resulting strings
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt
new file mode 100644
index 00000000..818a792b
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.operators03
+
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import kotlinx.coroutines.selects.*
+import org.reactivestreams.*
+import kotlin.coroutines.*
+
+fun <T, U> Publisher<T>.takeUntil(context: CoroutineContext, other: Publisher<U>) = publish<T>(context) {
+ this@takeUntil.openSubscription().consume { // explicitly open channel to Publisher<T>
+ val current = this
+ other.openSubscription().consume { // explicitly open channel to Publisher<U>
+ val other = this
+ whileSelect {
+ other.onReceive { false } // bail out on any received element from `other`
+ current.onReceive { send(it); true } // resend element from this channel and continue
+ }
+ }
+ }
+}
+
+fun CoroutineScope.rangeWithInterval(time: Long, start: Int, count: Int) = publish<Int> {
+ for (x in start until start + count) {
+ delay(time) // wait before sending each number
+ send(x)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ val slowNums = rangeWithInterval(200, 1, 10) // numbers with 200ms interval
+ val stop = rangeWithInterval(500, 1, 10) // the first one after 500ms
+ slowNums.takeUntil(Dispatchers.Unconfined, stop).collect { println(it) } // let's test it
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt
new file mode 100644
index 00000000..12d9c1f6
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.operators04
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.reactivestreams.*
+import kotlin.coroutines.*
+
+fun <T> Publisher<Publisher<T>>.merge(context: CoroutineContext) = publish<T>(context) {
+ collect { pub -> // for each publisher collected
+ launch { // launch a child coroutine
+ pub.collect { send(it) } // resend all element from this publisher
+ }
+ }
+}
+
+fun CoroutineScope.rangeWithInterval(time: Long, start: Int, count: Int) = publish<Int> {
+ for (x in start until start + count) {
+ delay(time) // wait before sending each number
+ send(x)
+ }
+}
+
+fun CoroutineScope.testPub() = publish<Publisher<Int>> {
+ send(rangeWithInterval(250, 1, 4)) // number 1 at 250ms, 2 at 500ms, 3 at 750ms, 4 at 1000ms
+ delay(100) // wait for 100 ms
+ send(rangeWithInterval(500, 11, 3)) // number 11 at 600ms, 12 at 1100ms, 13 at 1600ms
+ delay(1100) // wait for 1.1s - done in 1.2 sec after start
+}
+
+fun main() = runBlocking<Unit> {
+ testPub().merge(Dispatchers.Unconfined).collect { println(it) } // print the whole stream
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/test/GuideReactiveTest.kt b/reactive/kotlinx-coroutines-rx2/test/guide/test/GuideReactiveTest.kt
new file mode 100644
index 00000000..cebfc7b4
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/test/GuideReactiveTest.kt
@@ -0,0 +1,191 @@
+// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
+package kotlinx.coroutines.rx2.guide.test
+
+import kotlinx.coroutines.guide.test.*
+import org.junit.Test
+
+class GuideReactiveTest : ReactiveTestBase() {
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic01() {
+ test("KotlinxCoroutinesRx2GuideBasic01") { kotlinx.coroutines.rx2.guide.basic01.main() }.verifyLines(
+ "Elements:",
+ "Begin",
+ "1",
+ "2",
+ "3",
+ "Again:"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic02() {
+ test("KotlinxCoroutinesRx2GuideBasic02") { kotlinx.coroutines.rx2.guide.basic02.main() }.verifyLines(
+ "Elements:",
+ "Begin",
+ "1",
+ "2",
+ "3",
+ "Again:",
+ "Begin",
+ "1",
+ "2",
+ "3"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic03() {
+ test("KotlinxCoroutinesRx2GuideBasic03") { kotlinx.coroutines.rx2.guide.basic03.main() }.verifyLines(
+ "OnSubscribe",
+ "1",
+ "2",
+ "3",
+ "Finally"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic04() {
+ test("KotlinxCoroutinesRx2GuideBasic04") { kotlinx.coroutines.rx2.guide.basic04.main() }.verifyLines(
+ "OnSubscribe",
+ "1",
+ "2",
+ "3",
+ "OnComplete",
+ "Finally",
+ "4",
+ "5"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic05() {
+ test("KotlinxCoroutinesRx2GuideBasic05") { kotlinx.coroutines.rx2.guide.basic05.main() }.verifyLines(
+ "Sent 1",
+ "Processed 1",
+ "Sent 2",
+ "Processed 2",
+ "Sent 3",
+ "Processed 3",
+ "Complete"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic06() {
+ test("KotlinxCoroutinesRx2GuideBasic06") { kotlinx.coroutines.rx2.guide.basic06.main() }.verifyLines(
+ "two",
+ "three",
+ "four"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic07() {
+ test("KotlinxCoroutinesRx2GuideBasic07") { kotlinx.coroutines.rx2.guide.basic07.main() }.verifyLines(
+ "two",
+ "three",
+ "four"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic08() {
+ test("KotlinxCoroutinesRx2GuideBasic08") { kotlinx.coroutines.rx2.guide.basic08.main() }.verifyLines(
+ "four"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideBasic09() {
+ test("KotlinxCoroutinesRx2GuideBasic09") { kotlinx.coroutines.rx2.guide.basic09.main() }.verifyLines(
+ "four"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideOperators01() {
+ test("KotlinxCoroutinesRx2GuideOperators01") { kotlinx.coroutines.rx2.guide.operators01.main() }.verifyLines(
+ "1",
+ "2",
+ "3",
+ "4",
+ "5"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideOperators02() {
+ test("KotlinxCoroutinesRx2GuideOperators02") { kotlinx.coroutines.rx2.guide.operators02.main() }.verifyLines(
+ "2 is even",
+ "4 is even"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideOperators03() {
+ test("KotlinxCoroutinesRx2GuideOperators03") { kotlinx.coroutines.rx2.guide.operators03.main() }.verifyLines(
+ "1",
+ "2"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideOperators04() {
+ test("KotlinxCoroutinesRx2GuideOperators04") { kotlinx.coroutines.rx2.guide.operators04.main() }.verifyLines(
+ "1",
+ "2",
+ "11",
+ "3",
+ "4",
+ "12",
+ "13"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideContext01() {
+ test("KotlinxCoroutinesRx2GuideContext01") { kotlinx.coroutines.rx2.guide.context01.main() }.verifyLinesFlexibleThread(
+ "1 on thread RxComputationThreadPool-1",
+ "2 on thread RxComputationThreadPool-1",
+ "3 on thread RxComputationThreadPool-1"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideContext02() {
+ test("KotlinxCoroutinesRx2GuideContext02") { kotlinx.coroutines.rx2.guide.context02.main() }.verifyLinesStart(
+ "1 on thread ForkJoinPool.commonPool-worker-1",
+ "2 on thread ForkJoinPool.commonPool-worker-1",
+ "3 on thread ForkJoinPool.commonPool-worker-1"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideContext03() {
+ test("KotlinxCoroutinesRx2GuideContext03") { kotlinx.coroutines.rx2.guide.context03.main() }.verifyLinesFlexibleThread(
+ "1 on thread RxComputationThreadPool-1",
+ "2 on thread RxComputationThreadPool-1",
+ "3 on thread RxComputationThreadPool-1"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideContext04() {
+ test("KotlinxCoroutinesRx2GuideContext04") { kotlinx.coroutines.rx2.guide.context04.main() }.verifyLinesStart(
+ "1 on thread main",
+ "2 on thread main",
+ "3 on thread main"
+ )
+ }
+
+ @Test
+ fun testKotlinxCoroutinesRx2GuideContext05() {
+ test("KotlinxCoroutinesRx2GuideContext05") { kotlinx.coroutines.rx2.guide.context05.main() }.verifyLinesStart(
+ "1 on thread RxComputationThreadPool-1",
+ "2 on thread RxComputationThreadPool-1",
+ "3 on thread RxComputationThreadPool-1"
+ )
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/test/ReactiveTestBase.kt b/reactive/kotlinx-coroutines-rx2/test/guide/test/ReactiveTestBase.kt
new file mode 100644
index 00000000..3bedb62b
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/test/ReactiveTestBase.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx2.guide.test
+
+import io.reactivex.*
+import io.reactivex.disposables.*
+import io.reactivex.plugins.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.guide.test.*
+import org.junit.*
+import java.util.concurrent.*
+
+open class ReactiveTestBase {
+ @Before
+ fun setup() {
+ RxJavaPlugins.setIoSchedulerHandler(Handler)
+ RxJavaPlugins.setComputationSchedulerHandler(Handler)
+ ignoreLostThreads(
+ "RxComputationThreadPool-",
+ "RxCachedThreadScheduler-",
+ "RxCachedWorkerPoolEvictor-",
+ "RxSchedulerPurge-")
+ }
+
+ @After
+ fun tearDown() {
+ RxJavaPlugins.setIoSchedulerHandler(null)
+ RxJavaPlugins.setComputationSchedulerHandler(null)
+ }
+}
+
+private object Handler : io.reactivex.functions.Function<Scheduler, Scheduler> {
+ override fun apply(t: Scheduler): Scheduler = WrapperScheduler(t)
+}
+
+private class WrapperScheduler(private val scheduler: Scheduler) : Scheduler() {
+ override fun createWorker(): Worker = WrapperWorker(scheduler.createWorker())
+}
+
+private class WrapperWorker(private val worker: Scheduler.Worker) : Scheduler.Worker() {
+ override fun isDisposed(): Boolean = worker.isDisposed
+ override fun dispose() = worker.dispose()
+ override fun schedule(run: Runnable, delay: Long, unit: TimeUnit): Disposable =
+ worker.schedule(wrapTask(run), delay, unit)
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..aa5c68f9
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+rootProject.name = 'kotlinx.coroutines'
+enableFeaturePreview('GRADLE_METADATA')
+
+def module(String path) {
+ int i = path.lastIndexOf('/')
+ def name = path.substring(i + 1)
+ include(name)
+ project(":$name").projectDir = file(path)
+}
+
+// ---------------------------
+
+include('benchmarks')
+include('knit')
+include('site')
+
+module('binary-compatibility-validator')
+
+include "kotlinx-coroutines-core"
+
+module('kotlinx-coroutines-test')
+module('kotlinx-coroutines-debug')
+module('stdlib-stubs')
+module('kotlinx-coroutines-bom')
+
+
+module('integration/kotlinx-coroutines-guava')
+module('integration/kotlinx-coroutines-jdk8')
+module('integration/kotlinx-coroutines-slf4j')
+module('integration/kotlinx-coroutines-play-services')
+
+module('reactive/kotlinx-coroutines-reactive')
+module('reactive/kotlinx-coroutines-reactor')
+module('reactive/kotlinx-coroutines-rx2')
+module('reactive/kotlinx-coroutines-rx-example')
+
+module('ui/kotlinx-coroutines-android')
+module('ui/kotlinx-coroutines-android/android-unit-tests')
+module('ui/kotlinx-coroutines-javafx')
+module('ui/kotlinx-coroutines-swing')
+
+module('js/js-stub')
+module('js/example-frontend-js')
diff --git a/site/README.md b/site/README.md
new file mode 100644
index 00000000..8533dbf4
--- /dev/null
+++ b/site/README.md
@@ -0,0 +1,14 @@
+# Reference documentation site
+
+This module builds references documentation.
+
+## Building
+
+* Install [Jekyll](https://jekyllrb.com)
+* If you already have Ruby/Jekyll installed you might need to update its version:
+ * `cd site/docs`
+ * `bundle install`
+* In project root directory do:
+ * Run `./gradlew site`
+* The result is in `site/build/gh-pages/_site`
+* Upload it to github pages (`gh-pages` branch)
diff --git a/site/build.gradle b/site/build.gradle
new file mode 100644
index 00000000..796fcaca
--- /dev/null
+++ b/site/build.gradle
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+def buildDocsDir = "$buildDir/docs"
+
+task copyDocs(type: Copy, dependsOn: rootProject.getTasksByName("dokka", true)) {
+ from (rootProject.getTasksByName("dokka", true).collect { "$it.project.buildDir/dokka" }) {
+ include "**/*.md"
+ include "**/package-list"
+ }
+ from "docs"
+ into buildDocsDir
+}
+
+task copyExampleFrontendJs(type: Copy, dependsOn: ':example-frontend-js:bundle') {
+ def srcBuildDir = project(':example-frontend-js').buildDir
+ from "$srcBuildDir/dist"
+ into "$buildDocsDir/example-frontend-js"
+}
+
+task site(type: Exec, description: 'Generate github pages', dependsOn: [copyDocs, copyExampleFrontendJs]) {
+ inputs.files(fileTree(buildDocsDir))
+ outputs.dir("$buildDir/dist")
+ workingDir file(buildDocsDir)
+ commandLine 'bundle', 'exec', 'jekyll', 'build'
+}
+
+task clean(type: Delete) {
+ delete buildDir
+}
+
diff --git a/site/deploy.sh b/site/deploy.sh
new file mode 100755
index 00000000..de7cdef2
--- /dev/null
+++ b/site/deploy.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+
+# 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"
+PAGES_DIR="$BUILD_DIR/pages"
+
+# Init options
+GRADLE_OPT=
+PUSH_OPT=
+
+# Set dry run if needed
+if [ "$2" == "push" ] ; then
+ echo "--- Doing LIVE site deployment, so do clean build"
+ GRADLE_OPT=clean
+else
+ echo "--- Doing dry-run. To commit do 'deploy.sh <version> push'"
+ PUSH_OPT=--dry-run
+fi
+
+# Makes sure that site is built
+"$ROOT_DIR/gradlew" $GRADLE_OPT site
+
+# Cleanup dist directory (and ignore errors)
+rm -rf "$PAGES_DIR" || true
+
+# Prune worktrees to avoid errors from previous attempts
+git --work-tree "$ROOT_DIR" worktree prune
+
+# Create git worktree for gh-pages branch
+git --work-tree "$ROOT_DIR" worktree add -B gh-pages "$PAGES_DIR" origin/gh-pages
+
+# Now work in newly created workspace
+cd "$PAGES_DIR"
+
+# Fixup all the old documentation files
+# 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
+
+# Copy new documentation from dist
+cp -r "$DIST_DIR"/* "$PAGES_DIR"
+
+# Add it all to git
+git add *
+
+# Commit docs for the new version
+if [ -z "$1" ] ; then
+ echo "No argument with version supplied -- skipping commit"
+else
+ git commit -m "Version $1 docs"
+ git push $PUSH_OPT origin gh-pages:gh-pages
+fi
diff --git a/site/docs/Gemfile b/site/docs/Gemfile
new file mode 100644
index 00000000..dcf29c30
--- /dev/null
+++ b/site/docs/Gemfile
@@ -0,0 +1,16 @@
+source "https://rubygems.org"
+ruby RUBY_VERSION
+
+# Hello! This is where you manage which Jekyll version is used to run.
+# When you want to use a different version, change it below, save the
+# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
+#
+# bundle exec jekyll serve
+#
+# This will help ensure the proper Jekyll version is running.
+# Happy Jekylling!
+
+gem "jekyll", "3.6.3"
+
+# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
diff --git a/site/docs/Gemfile.lock b/site/docs/Gemfile.lock
new file mode 100644
index 00000000..f7f42451
--- /dev/null
+++ b/site/docs/Gemfile.lock
@@ -0,0 +1,64 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.5.2)
+ public_suffix (>= 2.0.2, < 4.0)
+ colorator (1.1.0)
+ ffi (1.9.25)
+ ffi (1.9.25-x64-mingw32)
+ forwardable-extended (2.6.0)
+ jekyll (3.6.3)
+ addressable (~> 2.4)
+ colorator (~> 1.0)
+ jekyll-sass-converter (~> 1.0)
+ jekyll-watch (~> 1.1)
+ kramdown (~> 1.14)
+ liquid (~> 4.0)
+ mercenary (~> 0.3.3)
+ pathutil (~> 0.9)
+ rouge (>= 1.7, < 3)
+ safe_yaml (~> 1.0)
+ jekyll-sass-converter (1.5.2)
+ sass (~> 3.4)
+ jekyll-watch (1.5.1)
+ listen (~> 3.0)
+ kramdown (1.17.0)
+ liquid (4.0.1)
+ listen (3.1.5)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ ruby_dep (~> 1.2)
+ mercenary (0.3.6)
+ pathutil (0.16.2)
+ forwardable-extended (~> 2.6)
+ public_suffix (3.0.3)
+ rb-fsevent (0.10.3)
+ rb-inotify (0.9.10)
+ ffi (>= 0.5.0, < 2)
+ rouge (2.2.1)
+ ruby_dep (1.5.0)
+ safe_yaml (1.0.4)
+ sass (3.7.2)
+ sass-listen (~> 4.0.0)
+ sass-listen (4.0.0)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ thread_safe (0.3.6)
+ tzinfo (1.2.4)
+ thread_safe (~> 0.1)
+ tzinfo-data (1.2017.3)
+ tzinfo (>= 1.0.0)
+
+PLATFORMS
+ ruby
+ x64-mingw32
+
+DEPENDENCIES
+ jekyll (= 3.6.3)
+ tzinfo-data
+
+RUBY VERSION
+ ruby 2.3.7p456
+
+BUNDLED WITH
+ 1.16.1
diff --git a/site/docs/_config.yml b/site/docs/_config.yml
new file mode 100644
index 00000000..978e0fb2
--- /dev/null
+++ b/site/docs/_config.yml
@@ -0,0 +1,17 @@
+# 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
+exclude:
+ - Gemfile
+ - Gemfile.lock
+include:
+ - package-list
diff --git a/site/docs/_includes/footer.html b/site/docs/_includes/footer.html
new file mode 100644
index 00000000..b115703c
--- /dev/null
+++ b/site/docs/_includes/footer.html
@@ -0,0 +1,3 @@
+<footer class="site-footer">
+ <!-- empty -->
+</footer>
diff --git a/site/docs/_includes/head.html b/site/docs/_includes/head.html
new file mode 100644
index 00000000..ca3227d6
--- /dev/null
+++ b/site/docs/_includes/head.html
@@ -0,0 +1,19 @@
+<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
new file mode 100644
index 00000000..b250a177
--- /dev/null
+++ b/site/docs/_includes/header.html
@@ -0,0 +1,5 @@
+<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
new file mode 100644
index 00000000..0f93a6b2
--- /dev/null
+++ b/site/docs/_layouts/api.html
@@ -0,0 +1,14 @@
+<!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
new file mode 100644
index 00000000..ae411495
--- /dev/null
+++ b/site/docs/_sass/_api.scss
@@ -0,0 +1,225 @@
+
+// ----------------- 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
new file mode 100644
index 00000000..b8d70d52
--- /dev/null
+++ b/site/docs/_sass/_base.scss
@@ -0,0 +1,97 @@
+// 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
new file mode 100644
index 00000000..d85d0592
--- /dev/null
+++ b/site/docs/_sass/_layout.scss
@@ -0,0 +1,37 @@
+// 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
new file mode 100644
index 00000000..86079f85
--- /dev/null
+++ b/site/docs/_sass/_minima.scss
@@ -0,0 +1,35 @@
+// 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
new file mode 100644
index 00000000..9b1d0347
--- /dev/null
+++ b/site/docs/assets/js/api.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+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
new file mode 100644
index 00000000..1be8487f
--- /dev/null
+++ b/site/docs/assets/main.scss
@@ -0,0 +1,36 @@
+---
+# 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
new file mode 100644
index 00000000..3e6bb934
--- /dev/null
+++ b/site/docs/index.md
@@ -0,0 +1,32 @@
+---
+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
new file mode 100644
index 00000000..f47bccc8
--- /dev/null
+++ b/stdlib-stubs/README.md
@@ -0,0 +1 @@
+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 b/stdlib-stubs/build.gradle
new file mode 100644
index 00000000..4d7eee1e
--- /dev/null
+++ b/stdlib-stubs/build.gradle
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+compileKotlin {
+ kotlinOptions {
+ freeCompilerArgs += "-Xallow-kotlin-package"
+ }
+}
diff --git a/stdlib-stubs/src/Continuation.kt b/stdlib-stubs/src/Continuation.kt
new file mode 100644
index 00000000..d5834dac
--- /dev/null
+++ b/stdlib-stubs/src/Continuation.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2018 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>)
+} \ No newline at end of file
diff --git a/stdlib-stubs/src/ContinuationInterceptor.kt b/stdlib-stubs/src/ContinuationInterceptor.kt
new file mode 100644
index 00000000..6fbfa70a
--- /dev/null
+++ b/stdlib-stubs/src/ContinuationInterceptor.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2018 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 {
+ 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
new file mode 100644
index 00000000..e2631547
--- /dev/null
+++ b/stdlib-stubs/src/CoroutineContext.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2018 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
new file mode 100644
index 00000000..5fe48cb7
--- /dev/null
+++ b/stdlib-stubs/src/Result.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlin
+
+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/README.md b/ui/README.md
new file mode 100644
index 00000000..0417ff92
--- /dev/null
+++ b/ui/README.md
@@ -0,0 +1,11 @@
+# Coroutines for UI
+
+This directory contains modules for coroutine programming with various single-threaded UI libraries.
+After adding dependency to the UI library, corresponding UI dispatcher will be available via `Dispatchers.Main`.
+Module name below corresponds to the artifact name in Maven/Gradle.
+
+## Modules
+
+* [kotlinx-coroutines-android](kotlinx-coroutines-android/README.md) -- `Dispatchers.Main` context for Android applications.
+* [kotlinx-coroutines-javafx](kotlinx-coroutines-javafx/README.md) -- `Dispatchers.JavaFx` context for JavaFX UI applications.
+* [kotlinx-coroutines-swing](kotlinx-coroutines-swing/README.md) -- `Dispatchers.Swing` context for Swing UI applications.
diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md
new file mode 100644
index 00000000..049c9441
--- /dev/null
+++ b/ui/coroutines-guide-ui.md
@@ -0,0 +1,725 @@
+<!--- INCLUDE .*/example-ui-([a-z]+)-([0-9]+)\.kt
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.$$1$$2
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+-->
+<!--- KNIT kotlinx-coroutines-javafx/test/guide/.*\.kt -->
+
+# 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
+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
+is confined, and all updates to the UI has to happen in this particular thread. With respect to coroutines,
+it means that you need an appropriate _coroutine dispatcher context_ that confines the coroutine
+execution to this main UI thread.
+
+In particular, `kotlinx.coroutines` has three modules that provide coroutine context for
+different UI application libraries:
+
+* [kotlinx-coroutines-android](kotlinx-coroutines-android) -- `Dispatchers.Main` context for Android applications.
+* [kotlinx-coroutines-javafx](kotlinx-coroutines-javafx) -- `Dispatchers.JavaFx` context for JavaFX UI applications.
+* [kotlinx-coroutines-swing](kotlinx-coroutines-swing) -- `Dispatchers.Swing` context for Swing UI applications.
+
+Also, UI dispatcher is available via `Dispatchers.Main` from `kotlinx-coroutines-core` and corresponding
+implementation (Android, JavaFx or Swing) is discovered by [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) API.
+For example, if you are writing JavaFx application, you can use either `Dispatchers.Main` or `Dispachers.JavaFx` extension, it will be the same object.
+
+This guide covers all UI libraries simultaneously, because each of these modules consists of just one
+object definition that is a couple of pages long. You can use any of them as an example to write the corresponding
+context object for your favourite UI library, even if it is not included out of the box here.
+
+## Table of contents
+
+<!--- TOC -->
+
+* [Setup](#setup)
+ * [JavaFx](#javafx)
+ * [Android](#android)
+* [Basic UI coroutines](#basic-ui-coroutines)
+ * [Launch UI coroutine](#launch-ui-coroutine)
+ * [Cancel UI coroutine](#cancel-ui-coroutine)
+* [Using actors within UI context](#using-actors-within-ui-context)
+ * [Extensions for coroutines](#extensions-for-coroutines)
+ * [At most one concurrent job](#at-most-one-concurrent-job)
+ * [Event conflation](#event-conflation)
+* [Blocking operations](#blocking-operations)
+ * [The problem of UI freezes](#the-problem-of-ui-freezes)
+ * [Structured concurrency, lifecycle and coroutine parent-child hierarchy](#structured-concurrency-lifecycle-and-coroutine-parent-child-hierarchy)
+ * [Blocking operations](#blocking-operations)
+* [Advanced topics](#advanced-topics)
+ * [Starting coroutine in UI event handlers without dispatch](#starting-coroutine-in-ui-event-handlers-without-dispatch)
+
+<!--- END_TOC -->
+
+## Setup
+
+The runnable examples in this guide are presented for JavaFx. The advantage is that all the examples can
+be directly started on any OS without the need for emulators or anything like that and they are fully self-contained
+(each example is in one file).
+There are separate notes on what changes need to be made (if any) to reproduce them on Android.
+
+### JavaFx
+
+The basic example application for JavaFx consists of a window with a text label named `hello` that initially
+contains "Hello World!" string and a pinkish circle in the bottom-right corner named `fab` (floating action button).
+
+![UI example for JavaFx](ui-example-javafx.png)
+
+The `start` function of JavaFX application invokes `setup` function, passing it reference to `hello` and `fab`
+nodes. That is where various code is placed in the rest of this guide:
+
+```kotlin
+fun setup(hello: Text, fab: Circle) {
+ // placeholder
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt).
+
+You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your
+workstation and open the project in IDE. All the examples from this guide are in the test folder of
+[`ui/kotlinx-coroutines-javafx`](kotlinx-coroutines-javafx) module.
+This way you'll be able to run and see how each example works and to
+experiment with them by making changes.
+
+### Android
+
+Follow the guide on [Getting Started With Android and Kotlin](https://kotlinlang.org/docs/tutorials/kotlin-android.html)
+to create Kotlin project in Android Studio. You are also encouraged to add
+[Kotlin Android Extensions](https://kotlinlang.org/docs/tutorials/android-plugin.html)
+to your application.
+
+In Android Studio 2.3 you'll get an application that looks similarly to the one shown below:
+
+![UI example for Android](ui-example-android.png)
+
+Go to the `context_main.xml` of your application and assign an ID of "hello" to the text view with "Hello World!" string,
+so that it is available in your application as `hello` with Kotlin Android extensions. The pinkish floating
+action button is already named `fab` in the project template that gets created.
+
+In the `MainActivity.kt` of your application remove the block `fab.setOnClickListener { ... }` and instead
+add `setup(hello, fab)` invocation as the last line of `onCreate` function.
+Create a placeholder `setup` function at the end of the file.
+That is where various code is placed in the rest of this guide:
+
+```kotlin
+fun setup(hello: TextView, fab: FloatingActionButton) {
+ // placeholder
+}
+```
+
+<!--- CLEAR -->
+
+Add dependencies on `kotlinx-coroutines-android` module to the `dependencies { ... }` section of
+`app/build.gradle` file:
+
+```groovy
+implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"
+```
+
+You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your
+workstation. The resulting template project for Android resides in
+[`ui/kotlinx-coroutines-android/example-app`](kotlinx-coroutines-android/example-app) directory.
+You can load it in Android Studio to follow this guide on Android.
+
+## Basic UI coroutines
+
+This section shows basic usage of coroutines in UI applications.
+
+### Launch UI coroutine
+
+The `kotlinx-coroutines-javafx` module contains
+[Dispatchers.JavaFx][kotlinx.coroutines.Dispatchers.JavaFx]
+dispatcher that dispatches coroutine execution to
+the JavaFx application thread. We import it as `Main` to make all the presented examples
+easily portable to Android:
+
+```kotlin
+import kotlinx.coroutines.javafx.JavaFx as Main
+```
+
+<!--- CLEAR -->
+
+Coroutines confined to the main UI thread can freely update anything in UI and suspend without blocking the main thread.
+For example, we can perform animations by coding them in imperative style. The following code updates the
+text with a 10 to 1 countdown twice a second, using [launch] coroutine builder:
+
+```kotlin
+fun setup(hello: Text, fab: Circle) {
+ GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread
+ for (i in 10 downTo 1) { // countdown from 10 to 1
+ hello.text = "Countdown $i ..." // update text
+ delay(500) // wait half a second
+ }
+ hello.text = "Done!"
+ }
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt).
+
+So, what happens here? Because we are launching coroutine in the main UI context, we can freely update UI from
+inside this coroutine and invoke _suspending functions_ like [delay] at the same time. UI is not frozen
+while `delay` waits, because it does not block the UI thread -- it just suspends the coroutine.
+
+> The corresponding code for Android application is the same.
+ You just need to copy the body of `setup` function into the corresponding function of Android project.
+
+### Cancel UI coroutine
+
+We can keep a reference to the [Job] object that `launch` function returns and use it to cancel
+coroutine when we want to stop it. Let us cancel the coroutine when pinkish circle is clicked:
+
+```kotlin
+fun setup(hello: Text, fab: Circle) {
+ val job = GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread
+ for (i in 10 downTo 1) { // countdown from 10 to 1
+ hello.text = "Countdown $i ..." // update text
+ delay(500) // wait half a second
+ }
+ hello.text = "Done!"
+ }
+ fab.onMouseClicked = EventHandler { job.cancel() } // cancel coroutine on click
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt).
+
+Now, if the circle is clicked while countdown is still running, the countdown stops.
+Note that [Job.cancel] is completely thread-safe and non-blocking. It just signals the coroutine to cancel
+its job, without waiting for it to actually terminate. It can be invoked from anywhere.
+Invoking it on a coroutine that was already cancelled or has completed does nothing.
+
+> The corresponding line for Android is shown below:
+
+```kotlin
+fab.setOnClickListener { job.cancel() } // cancel coroutine on click
+```
+
+<!--- CLEAR -->
+
+## Using actors within UI context
+
+In this section we show how UI applications can use actors within their UI context make sure that
+there is no unbounded growth in the number of launched coroutines.
+
+### Extensions for coroutines
+
+Our goal is to write an extension _coroutine builder_ function named `onClick`,
+so that we can perform countdown animation every time when the circle is clicked with this simple code:
+
+```kotlin
+fun setup(hello: Text, fab: Circle) {
+ fab.onClick { // start coroutine when the circle is clicked
+ for (i in 10 downTo 1) { // countdown from 10 to 1
+ hello.text = "Countdown $i ..." // update text
+ delay(500) // wait half a second
+ }
+ hello.text = "Done!"
+ }
+}
+```
+
+<!--- INCLUDE .*/example-ui-actor-([0-9]+).kt -->
+
+Our first implementation for `onClick` just launches a new coroutine on each mouse event and
+passes the corresponding mouse event into the supplied action (just in case we need it):
+
+```kotlin
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ onMouseClicked = EventHandler { event ->
+ GlobalScope.launch(Dispatchers.Main) {
+ action(event)
+ }
+ }
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt).
+
+Note that each time the circle is clicked, it starts a new coroutine and they all compete to
+update the text. Try it. It does not look very good. We'll fix it later.
+
+> On Android, the corresponding extension can be written for `View` class, so that the code
+ in `setup` function that is shown above can be used without changes. There is no `MouseEvent`
+ used in OnClickListener on Android, so it is omitted.
+
+```kotlin
+fun View.onClick(action: suspend () -> Unit) {
+ setOnClickListener {
+ GlobalScope.launch(Dispatchers.Main) {
+ action()
+ }
+ }
+}
+```
+
+<!--- CLEAR -->
+
+### At most one concurrent job
+
+We can cancel an active job before starting a new one to ensure that at most one coroutine is animating
+the countdown. However, it is generally not the best idea. The [cancel][Job.cancel] function serves only as a signal
+to abort a coroutine. Cancellation is cooperative and a coroutine may, at the moment, be doing something non-cancellable
+or otherwise ignore a cancellation signal. A better solution is to use an [actor] for tasks that should
+not be performed concurrently. Let us change `onClick` extension implementation:
+
+```kotlin
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ // launch one actor to handle all events on this node
+ val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main) {
+ for (event in channel) action(event) // pass event to action
+ }
+ // install a listener to offer events to this actor
+ onMouseClicked = EventHandler { event ->
+ eventActor.offer(event)
+ }
+}
+```
+
+> You can get 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.
+
+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
+the `receive` is active.
+
+> On Android, there is `View` sent in OnClickListener, so we send the `View` to the actor as a signal.
+ The corresponding extension for `View` class looks like this:
+
+```kotlin
+fun View.onClick(action: suspend (View) -> Unit) {
+ // launch one actor
+ val eventActor = GlobalScope.actor<View>(Dispatchers.Main) {
+ for (event in channel) action(event)
+ }
+ // install a listener to activate this actor
+ setOnClickListener {
+ eventActor.offer(it)
+ }
+}
+```
+
+<!--- CLEAR -->
+
+
+### Event conflation
+
+Sometimes it is more appropriate to process the most recent event, instead of just ignoring events while we were busy
+processing the previous one. The [actor] coroutine builder accepts an optional `capacity` parameter that
+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 `ConflatedChannel` by passing [Channel.CONFLATED] capacity value. The
+change is only to the line that creates an actor:
+
+```kotlin
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ // launch one actor to handle all events on this node
+ 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
+ onMouseClicked = EventHandler { event ->
+ eventActor.offer(event)
+ }
+}
+```
+
+> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt).
+ On Android you need to update `val eventActor = ...` line from the previous example.
+
+Now, if a circle is clicked while the animation is running, it restarts animation after the end of it. Just once.
+Repeated clicks while the animation is running are _conflated_ and only the most recent event gets to be
+processed.
+
+This is also a desired behaviour for UI applications that have to react to incoming high-frequency
+event streams by updating their UI based on the most recently received update. A coroutine that is using
+`ConflatedChannel` avoids delays that are usually introduced by buffering of events.
+
+You can experiment with `capacity` parameter in the above line to see how it affects the behaviour of the code.
+Setting `capacity = Channel.UNLIMITED` creates a coroutine with `LinkedListChannel` mailbox that buffers all
+events. In this case, the animation runs as many times as the circle is clicked.
+
+## Blocking operations
+
+This section explains how to use UI coroutines with thread-blocking operations.
+
+### The problem of UI freezes
+
+It would have been great if all APIs out there were written as suspending functions that never blocks an
+execution thread. However, it is quite often not the case. Sometimes you need to do a CPU-consuming computation
+or just need to invoke some 3rd party APIs for network access, for example, that blocks the invoker thread.
+You cannot do that from the main UI thread nor from the UI-confined coroutine directly, because that would
+block the main UI thread and cause the freeze up of the UI.
+
+<!--- INCLUDE .*/example-ui-blocking-([0-9]+).kt
+
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) {
+ for (event in channel) action(event) // pass event to action
+ }
+ onMouseClicked = EventHandler { event ->
+ eventActor.offer(event)
+ }
+}
+-->
+
+The following example illustrates the problem. We are going to use `onClick` extension with UI-confined
+event-conflating actor from the last section to process the last click in the main UI thread.
+For this example, we are going to
+perform naive computation of [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number):
+
+```kotlin
+fun fib(x: Int): Int =
+ if (x <= 1) x else fib(x - 1) + fib(x - 2)
+```
+
+We'll be computing larger and larger Fibonacci number each time the circle is clicked.
+To make the UI freeze more obvious, there is also a fast counting animation that is always running
+and is constantly updating the text in the main UI dispatcher:
+
+```kotlin
+fun setup(hello: Text, fab: Circle) {
+ var result = "none" // the last result
+ // counting animation
+ GlobalScope.launch(Dispatchers.Main) {
+ var counter = 0
+ while (true) {
+ hello.text = "${++counter}: $result"
+ delay(100) // update the text every 100ms
+ }
+ }
+ // compute the next fibonacci number of each click
+ var x = 1
+ fab.onClick {
+ result = "fib($x) = ${fib(x)}"
+ x++
+ }
+}
+```
+
+> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt).
+ You can just copy the `fib` function and the body of the `setup` function to your Android project.
+
+Try clicking on the circle in this example. After around 30-40th click our naive computation is going to become
+quite slow and you would immediately see how the main UI thread freezes, because the animation stops running
+during UI freeze.
+
+### Structured concurrency, lifecycle and coroutine parent-child hierarchy
+
+A typical UI application has a number of elements with a lifecycle. Windows, UI controls, activities, views, fragments
+and other visual elements are created and destroyed. A long-running coroutine, performing some IO or a background
+computation, can retain references to the corresponding UI elements for longer than it is needed, preventing garbage
+collection of the whole trees of UI objects that were already destroyed and will not be displayed anymore.
+
+The natural solution to this problem is to associate a [Job] object with each UI object that has a lifecycle and create
+all the coroutines in the context of this job. But passing associated job object to every coroutine builder is error-prone,
+it is easy to forget it. For this purpose, [CoroutineScope] interface could be implemented by UI owner, and then every
+coroutine builder defined as an extension on [CoroutineScope] inherits UI job without explicitly mentioning it.
+For the sake of simplicity, [MainScope()] factory can be used. It automatically provides `Dispatchers.Main` and parent
+job.
+
+For example, in Android application an `Activity` is initially _created_ and is _destroyed_ when it is no longer
+needed and when its memory must be released. A natural solution is to attach an
+instance of a `Job` to an instance of an `Activity`:
+<!--- CLEAR -->
+
+```kotlin
+abstract class ScopedAppActivity: AppCompatActivity(), CoroutineScope by MainScope() {
+ override fun onDestroy() {
+ super.onDestroy()
+ cancel() // CoroutineScope.cancel
+ }
+}
+```
+
+Now, an activity that is associated with a job has to extend ScopedAppActivity
+
+```kotlin
+class MainActivity : ScopedAppActivity() {
+
+ fun asyncShowData() = launch { // Is invoked in UI context with Activity's job as a parent
+ // actual implementation
+ }
+
+ suspend fun showIOData() {
+ val deferred = async(Dispatchers.IO) {
+ // impl
+ }
+ withContext(Dispatchers.Main) {
+ val data = deferred.await()
+ // Show data in UI
+ }
+ }
+}
+```
+
+Every coroutine launched from within a `MainActivity` has its job as a parent and is immediately cancelled when
+activity is destroyed.
+
+To propagate activity scope to its views and presenters, multiple techniques can be used:
+- [coroutineScope] builder to provide a nested scope
+- Receive [CoroutineScope] in presenter method parameters
+- Make method extension on [CoroutineScope] (applicable only for top-level methods)
+
+```kotlin
+class ActivityWithPresenters: ScopedAppActivity() {
+ fun init() {
+ val presenter = Presenter()
+ val presenter2 = ScopedPresenter(this)
+ }
+}
+
+class Presenter {
+ suspend fun loadData() = coroutineScope {
+ // Nested scope of outer activity
+ }
+
+ suspend fun loadData(uiScope: CoroutineScope) = uiScope.launch {
+ // Invoked in the uiScope
+ }
+}
+
+class ScopedPresenter(scope: CoroutineScope): CoroutineScope by scope {
+ fun loadData() = launch { // Extension on ActivityWithPresenters's scope
+ }
+}
+
+suspend fun CoroutineScope.launchInIO() = launch(Dispatchers.IO) {
+ // Launched in the scope of the caller, but with IO dispatcher
+}
+```
+
+Parent-child relation between jobs forms a hierarchy. A coroutine that performs some background job on behalf of
+the view and in its context 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.
+<!--- CLEAR -->
+
+### Blocking operations
+
+The fix for the blocking operations on the main UI thread is quite straightforward with coroutines. We'll
+convert our "blocking" `fib` function to a non-blocking suspending function that runs the computation in
+the background thread by using [withContext] function to change its execution context to [Dispatchers.Default] that is
+backed by the background pool of threads.
+Notice, that `fib` function is now marked with `suspend` modifier. It does not block the coroutine that
+it is invoked from anymore, but suspends its execution when the computation in the background thread is working:
+
+<!--- INCLUDE .*/example-ui-blocking-0[23].kt
+
+fun setup(hello: Text, fab: Circle) {
+ var result = "none" // the last result
+ // counting animation
+ GlobalScope.launch(Dispatchers.Main) {
+ var counter = 0
+ while (true) {
+ hello.text = "${++counter}: $result"
+ delay(100) // update the text every 100ms
+ }
+ }
+ // compute next fibonacci number of each click
+ var x = 1
+ fab.onClick {
+ result = "fib($x) = ${fib(x)}"
+ x++
+ }
+}
+-->
+
+```kotlin
+suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) {
+ if (x <= 1) x else fib(x - 1) + fib(x - 2)
+}
+```
+
+> You can get full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt).
+
+You can run this code and verify that UI is not frozen while large Fibonacci numbers are being computed.
+However, this code computes `fib` somewhat slower, because every recursive call to `fib` goes via `withContext`. This is
+not a big problem in practice, because `withContext` is smart enough to check that the coroutine is already running
+in the required context and avoids overhead of dispatching coroutine to a different thread again. It is an
+overhead nonetheless, which is visible on this primitive code that does nothing else, but only adds integers
+in between invocations to `withContext`. For some more substantial code, the overhead of an extra `withContext` invocation is
+not going to be significant.
+
+Still, this particular `fib` implementation can be made to run as fast as before, but in the background thread, by renaming
+the original `fib` function to `fibBlocking` and defining `fib` with `withContext` wrapper on top of `fibBlocking`:
+
+```kotlin
+suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) {
+ fibBlocking(x)
+}
+
+fun fibBlocking(x: Int): Int =
+ if (x <= 1) x else fibBlocking(x - 1) + fibBlocking(x - 2)
+```
+
+> You can get full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt).
+
+You can now enjoy full-speed naive Fibonacci computation without blocking the main UI thread.
+All we need is `withContext(Dispatchers.Default)`.
+
+Note that because the `fib` function is invoked from the single actor in our code, there is at most one concurrent
+computation of it at any given time, so this code has a natural limit on the resource utilization.
+It can saturate at most one CPU core.
+
+## Advanced topics
+
+This section covers various advanced topics.
+
+### Starting coroutine in UI event handlers without dispatch
+
+Let us write the following code in `setup` to visualize the order of execution when coroutine is launched
+from the UI thread:
+
+<!--- CLEAR -->
+
+```kotlin
+fun setup(hello: Text, fab: Circle) {
+ fab.onMouseClicked = EventHandler {
+ println("Before launch")
+ GlobalScope.launch(Dispatchers.Main) {
+ println("Inside coroutine")
+ delay(100)
+ println("After delay")
+ }
+ println("After launch")
+ }
+}
+```
+
+> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt).
+
+When we start this code and click on a pinkish circle, the following messages are printed to the console:
+
+```text
+Before launch
+After launch
+Inside coroutine
+After delay
+```
+
+As you can see, execution immediately continues after [launch], while the coroutine gets posted onto the main UI thread
+for execution later. All UI dispatchers in `kotlinx.coroutines` are implemented this way. Why so?
+
+Basically, the choice here is between "JS-style" asynchronous approach (async actions
+are always postponed to be executed later in the event dispatch thread) and "C#-style" approach
+(async actions are executed in the invoker thread until the first suspension point).
+While, C# approach seems to be more efficient, it ends up with recommendations like
+"use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent
+and does not require programmers to think about whether they need to yield or not.
+
+However, in this particular case when coroutine is started from an event handler and there is no other code around it,
+this extra dispatch does indeed add an extra overhead without bringing any additional value.
+In this case an optional [CoroutineStart] parameter to [launch], [async] and [actor] coroutine builders
+can be used for performance optimization.
+Setting it to the value of [CoroutineStart.UNDISPATCHED] has the effect of starting to execute
+coroutine immediately until its first suspension point as the following example shows:
+
+```kotlin
+fun setup(hello: Text, fab: Circle) {
+ fab.onMouseClicked = EventHandler {
+ println("Before launch")
+ GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { // <--- Notice this change
+ println("Inside coroutine")
+ delay(100) // <--- And this is where coroutine suspends
+ println("After delay")
+ }
+ println("After launch")
+ }
+}
+```
+
+> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt).
+
+It prints the following messages on click, confirming that code in the coroutine starts to execute immediately:
+
+```text
+Before launch
+Inside coroutine
+After launch
+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
+[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
+[coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-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
+<!--- 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]: 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
+<!--- 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
+<!--- MODULE kotlinx-coroutines-android -->
+<!--- INDEX kotlinx.coroutines.android -->
+<!--- END -->
diff --git a/ui/kotlinx-coroutines-android/README.md b/ui/kotlinx-coroutines-android/README.md
new file mode 100644
index 00000000..77bd2afb
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/README.md
@@ -0,0 +1,10 @@
+# Module kotlinx-coroutines-android
+
+Provides `Dispatchers.Main` context for Android applications.
+
+Read [Guide to UI programming with coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md)
+for tutorial on this module.
+
+# Package kotlinx.coroutines.android
+
+Provides `Dispatchers.Main` context for Android applications.
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle
new file mode 100644
index 00000000..83b8dcd7
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+repositories {
+ google()
+}
+
+dependencies {
+ testImplementation 'com.google.android:android:4.1.1.4'
+ testImplementation 'com.android.support:support-annotations:26.1.0'
+ testImplementation 'com.google.android:android:4.1.1.4'
+ testImplementation 'org.robolectric:robolectric:4.0-alpha-3'
+ testImplementation project(":kotlinx-coroutines-test")
+ testImplementation project(":kotlinx-coroutines-android")
+}
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/resources/META-INF/services/kotlinx.coroutines.CoroutineScope b/ui/kotlinx-coroutines-android/android-unit-tests/resources/META-INF/services/kotlinx.coroutines.CoroutineScope
new file mode 100644
index 00000000..2b6308a7
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/resources/META-INF/services/kotlinx.coroutines.CoroutineScope
@@ -0,0 +1,13 @@
+kotlinx.coroutines.android.EmptyCoroutineScopeImpl1
+kotlinx.coroutines.android.EmptyCoroutineScopeImpl2
+# testing configuration file parsing # kotlinx.coroutines.service.loader.LocalEmptyCoroutineScope2
+
+kotlinx.coroutines.android.EmptyCoroutineScopeImpl2
+
+kotlinx.coroutines.android.EmptyCoroutineScopeImpl1
+
+
+kotlinx.coroutines.android.EmptyCoroutineScopeImpl1
+
+
+kotlinx.coroutines.android.EmptyCoroutineScopeImpl3#comment \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt b/ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt
new file mode 100644
index 00000000..06779bf5
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt
@@ -0,0 +1,21 @@
+package kotlinx.coroutines.android
+
+import kotlinx.coroutines.CoroutineScope
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+// Classes for testing service loader
+internal class EmptyCoroutineScopeImpl1 : CoroutineScope {
+ override val coroutineContext: CoroutineContext
+ get() = EmptyCoroutineContext
+}
+
+internal class EmptyCoroutineScopeImpl2 : CoroutineScope {
+ override val coroutineContext: CoroutineContext
+ get() = EmptyCoroutineContext
+}
+
+internal class EmptyCoroutineScopeImpl3 : CoroutineScope {
+ override val coroutineContext: CoroutineContext
+ get() = EmptyCoroutineContext
+} \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/CustomizedRobolectricTest.kt b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/CustomizedRobolectricTest.kt
new file mode 100644
index 00000000..578cd741
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/CustomizedRobolectricTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package ordered.tests
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.*
+import org.junit.Test
+import org.junit.runner.*
+import org.robolectric.*
+import org.robolectric.annotation.*
+import org.robolectric.shadows.*
+import kotlin.test.*
+
+
+class InitMainDispatcherBeforeRobolectricTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
+
+ init {
+ kotlin.runCatching {
+ // touch Main, watch it burn
+ GlobalScope.launch(Dispatchers.Main + CoroutineExceptionHandler { _, _ -> }) { }
+ }
+ }
+}
+
+@Config(manifest = Config.NONE, sdk = [28])
+@RunWith(InitMainDispatcherBeforeRobolectricTestRunner::class)
+class CustomizedRobolectricTest {
+ @Test
+ fun testComponent() {
+ // Note that main is not set at all
+ val component = TestComponent()
+ checkComponent(component)
+ }
+
+ @Test
+ fun testComponentAfterReset() {
+ // Note that main is not set at all
+ val component = TestComponent()
+ Dispatchers.setMain(Dispatchers.Unconfined)
+ Dispatchers.resetMain()
+ checkComponent(component)
+ }
+
+
+ private fun checkComponent(component: TestComponent) {
+ val mainLooper = ShadowLooper.getShadowMainLooper()
+ mainLooper.pause()
+ component.launchSomething()
+ assertFalse(component.launchCompleted)
+ mainLooper.unPause()
+ assertTrue(component.launchCompleted)
+ }
+} \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstMockedMainTest.kt b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstMockedMainTest.kt
new file mode 100644
index 00000000..c134ec5f
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstMockedMainTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package ordered.tests
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.*
+import org.junit.*
+import org.junit.Test
+import java.lang.IllegalStateException
+import kotlin.test.*
+
+open class FirstMockedMainTest : TestBase() {
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(Dispatchers.Unconfined)
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun testComponent() {
+ val component = TestComponent()
+ component.launchSomething()
+ assertTrue(component.launchCompleted)
+ }
+
+ @Test
+ fun testFailureWhenReset() {
+ Dispatchers.resetMain()
+ val component = TestComponent()
+ try {
+ component.launchSomething()
+ throw component.caughtException
+ } catch (e: IllegalStateException) {
+ assertTrue(e.message!!.contains("Dispatchers.setMain from kotlinx-coroutines-test"))
+ }
+ }
+}
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstRobolectricTest.kt b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstRobolectricTest.kt
new file mode 100644
index 00000000..eab6fc17
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstRobolectricTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package ordered.tests
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.*
+import org.junit.Test
+import org.junit.runner.*
+import org.robolectric.*
+import org.robolectric.annotation.*
+import org.robolectric.shadows.*
+import kotlin.test.*
+
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest = Config.NONE, sdk = [28])
+open class FirstRobolectricTest {
+ @Test
+ fun testComponent() {
+ // Note that main is not set at all
+ val component = TestComponent()
+ checkComponent(component)
+ }
+
+ @Test
+ fun testComponentAfterReset() {
+ // Note that main is not set at all
+ val component = TestComponent()
+ Dispatchers.setMain(Dispatchers.Unconfined)
+ Dispatchers.resetMain()
+ checkComponent(component)
+ }
+
+ @Test
+ fun testDelay() {
+ val component = TestComponent()
+ val mainLooper = ShadowLooper.getShadowMainLooper()
+ mainLooper.pause()
+ component.launchDelayed()
+ mainLooper.runToNextTask()
+ assertFalse(component.delayedLaunchCompleted)
+ mainLooper.runToNextTask()
+ assertTrue(component.delayedLaunchCompleted)
+ }
+
+ private fun checkComponent(component: TestComponent) {
+ val mainLooper = ShadowLooper.getShadowMainLooper()
+ mainLooper.pause()
+ component.launchSomething()
+ assertFalse(component.launchCompleted)
+ mainLooper.unPause()
+ assertTrue(component.launchCompleted)
+ }
+}
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/MockedMainTest.kt b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/MockedMainTest.kt
new file mode 100644
index 00000000..d68d9eb6
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/MockedMainTest.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package ordered.tests
+
+class MockedMainTest : FirstMockedMainTest()
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/RobolectricTest.kt b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/RobolectricTest.kt
new file mode 100644
index 00000000..4213cb41
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/RobolectricTest.kt
@@ -0,0 +1,6 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package ordered.tests
+
+open class RobolectricTest : FirstRobolectricTest()
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/TestComponent.kt b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/TestComponent.kt
new file mode 100644
index 00000000..9cf813bc
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/TestComponent.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package ordered.tests
+
+import kotlinx.coroutines.*
+
+public class TestComponent {
+ internal lateinit var caughtException: Throwable
+ private val scope =
+ CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineExceptionHandler { _, e -> caughtException = e})
+ public var launchCompleted = false
+ public var delayedLaunchCompleted = false
+
+ fun launchSomething() {
+ scope.launch {
+ launchCompleted = true
+ }
+ }
+
+ fun launchDelayed() {
+ scope.launch {
+ delay(Long.MAX_VALUE)
+ delayedLaunchCompleted = true
+ }
+ }
+}
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle
new file mode 100644
index 00000000..b5919bea
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 27
+ defaultConfig {
+ applicationId "org.jetbrains.kotlinx.animation"
+ minSdkVersion 14
+ targetSdkVersion 27
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation 'com.android.support:appcompat-v7:27.1.1'
+ implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+ implementation 'com.android.support:design:27.1.1'
+ implementation 'android.arch.lifecycle:extensions:1.1.1'
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.1'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+}
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/proguard-rules.pro b/ui/kotlinx-coroutines-android/animation-app/app/proguard-rules.pro
new file mode 100644
index 00000000..aea920a6
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/proguard-rules.pro
@@ -0,0 +1,8 @@
+-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
+-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
+-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
+-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
+
+-keepclassmembernames class kotlinx.** {
+ volatile <fields>;
+}
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
new file mode 100644
index 00000000..7625b1bf
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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> \ No newline at end of file
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
new file mode 100644
index 00000000..dd4aafb1
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt
@@ -0,0 +1,155 @@
+package org.jetbrains.kotlinx.animation
+
+import android.arch.lifecycle.LifecycleOwner
+import android.arch.lifecycle.MutableLiveData
+import android.arch.lifecycle.Observer
+import android.arch.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
new file mode 100644
index 00000000..87a857cc
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt
@@ -0,0 +1,26 @@
+package org.jetbrains.kotlinx.animation
+
+import android.arch.lifecycle.ViewModelProviders
+import android.os.Bundle
+import android.support.v7.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
new file mode 100644
index 00000000..c7bd21db
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<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
new file mode 100644
index 00000000..d5fccc53
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?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
new file mode 100644
index 00000000..cfc022f1
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.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">
+
+ <android.support.design.widget.AppBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/AppTheme.AppBarOverlay">
+
+ <android.support.v7.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" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/content_main" />
+
+ <android.support.design.widget.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" />
+
+ <android.support.design.widget.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" />
+
+</android.support.design.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
new file mode 100644
index 00000000..02058bde
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.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" />
+
+</android.support.constraint.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
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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> \ No newline at end of file
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
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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> \ No newline at end of file
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
new file mode 100644
index 00000000..a2f59082
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher.png
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
new file mode 100644
index 00000000..1b523998
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..ff10afd6
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher.png
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
new file mode 100644
index 00000000..115a4c76
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..dcd3cd80
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
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
new file mode 100644
index 00000000..459ca609
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..8ca12fe0
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
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
new file mode 100644
index 00000000..8e19b410
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..b824ebdd
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
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
new file mode 100644
index 00000000..4c19a13c
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..9ad7e369
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?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
new file mode 100644
index 00000000..59a0b0c4
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+<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
new file mode 100644
index 00000000..cd3f467b
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<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
new file mode 100644
index 00000000..545b9c6d
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+<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 b/ui/kotlinx-coroutines-android/animation-app/build.gradle
new file mode 100644
index 00000000..f512a873
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/build.gradle
@@ -0,0 +1,26 @@
+// 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.4.1'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle.properties b/ui/kotlinx-coroutines-android/animation-app/gradle.properties
new file mode 100644
index 00000000..8e119d71
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/gradle.properties
@@ -0,0 +1,23 @@
+# 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.coroutines=enable
+
+kotlin_version=1.3.50
+coroutines_version=1.3.1
+
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
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.jar
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
new file mode 100644
index 00000000..caf54fa2
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradlew b/ui/kotlinx-coroutines-android/animation-app/gradlew
new file mode 100644
index 00000000..cccdd3d5
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## 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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# 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/ui/kotlinx-coroutines-android/animation-app/gradlew.bat b/ui/kotlinx-coroutines-android/animation-app/gradlew.bat
new file mode 100644
index 00000000..f9553162
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/gradlew.bat
@@ -0,0 +1,84 @@
+@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 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=
+
+@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 b/ui/kotlinx-coroutines-android/animation-app/settings.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/animation-app/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/ui/kotlinx-coroutines-android/build.gradle b/ui/kotlinx-coroutines-android/build.gradle
new file mode 100644
index 00000000..5537577d
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/build.gradle
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+repositories {
+ google()
+ // TODO Remove once R8 is updated to a 1.6.x version.
+ maven {
+ url "http://storage.googleapis.com/r8-releases/raw/master"
+ metadataSources {
+ artifact()
+ }
+ }
+}
+
+configurations {
+ r8
+}
+
+dependencies {
+ compileOnly 'com.google.android:android:4.1.1.4'
+ compileOnly 'com.android.support:support-annotations:26.1.0'
+
+ testImplementation 'com.google.android:android:4.1.1.4'
+ testImplementation 'org.robolectric:robolectric:4.0-alpha-3'
+ testImplementation 'org.smali:baksmali:2.2.7'
+
+ // TODO Replace with a 1.6.x version once released to maven.google.com.
+ r8 'com.android.tools:r8:a7ce65837bec81c62261bf0adac73d9c09d32af2'
+}
+
+class RunR8Task extends JavaExec {
+
+ @OutputDirectory
+ File outputDex
+
+ @InputFile
+ File inputConfig
+
+ @InputFile
+ final File inputConfigCommon = new File('r8-test-common.pro')
+
+ @InputFiles
+ final File jarFile = project.jar.archivePath
+
+ @Override
+ Task configure(Closure closure) {
+ super.configure(closure)
+ classpath = project.configurations.r8
+ main = 'com.android.tools.r8.R8'
+
+ def arguments = [
+ '--release',
+ '--no-desugaring',
+ '--output', outputDex.absolutePath,
+ '--pg-conf', inputConfig.absolutePath
+ ]
+ arguments.addAll(project.configurations.runtimeClasspath.files.collect { it.absolutePath })
+ arguments.addAll(jarFile.absolutePath)
+
+ args = arguments
+ return this
+ }
+
+ @Override
+ void exec() {
+ if (outputDex.exists()) {
+ outputDex.deleteDir()
+ }
+ outputDex.mkdirs()
+
+ super.exec()
+ }
+}
+
+def optimizedDex = new File(buildDir, "dex-optim/")
+def unOptimizedDex = new File(buildDir, "dex-unoptim/")
+
+task runR8(type: RunR8Task, dependsOn: 'jar'){
+ outputDex = optimizedDex
+ inputConfig = file('r8-test-rules.pro')
+}
+
+task runR8NoOptim(type: RunR8Task, dependsOn: 'jar'){
+ outputDex = unOptimizedDex
+ inputConfig = file('r8-test-rules-no-optim.pro')
+}
+
+test {
+ // Ensure the R8-processed dex is built and supply its path as a property to the test.
+ dependsOn(runR8)
+ dependsOn(runR8NoOptim)
+ def dex1 = new File(optimizedDex, "classes.dex")
+ def dex2 = new File(unOptimizedDex, "classes.dex")
+
+ inputs.files(dex1, dex2)
+
+ systemProperty 'dexPath', dex1.absolutePath
+ systemProperty 'noOptimDexPath', dex2.absolutePath
+}
+
+tasks.withType(dokka.getClass()) {
+ externalDocumentationLink {
+ url = new URL("https://developer.android.com/reference/")
+ packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
+ }
+} \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/example-app/.gitignore b/ui/kotlinx-coroutines-android/example-app/.gitignore
new file mode 100644
index 00000000..03d649e1
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/.gitignore
@@ -0,0 +1,7 @@
+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 b/ui/kotlinx-coroutines-android/example-app/app/build.gradle
new file mode 100644
index 00000000..98257d37
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 27
+ defaultConfig {
+ applicationId "com.example.app"
+ minSdkVersion 14
+ targetSdkVersion 27
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation 'com.android.support:appcompat-v7:27.1.1'
+ implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+ implementation 'com.android.support:design:27.1.1'
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.1'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+}
diff --git a/ui/kotlinx-coroutines-android/example-app/app/proguard-rules.pro b/ui/kotlinx-coroutines-android/example-app/app/proguard-rules.pro
new file mode 100644
index 00000000..aea920a6
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/proguard-rules.pro
@@ -0,0 +1,8 @@
+-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
+-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
+-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
+-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
+
+-keepclassmembernames class kotlinx.** {
+ volatile <fields>;
+}
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
new file mode 100644
index 00000000..a04a27e0
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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> \ No newline at end of file
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
new file mode 100644
index 00000000..fc1cdbff
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt
@@ -0,0 +1,39 @@
+package com.example.app
+
+import android.os.Bundle
+import android.support.design.widget.FloatingActionButton
+import android.support.v7.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
new file mode 100644
index 00000000..13d32252
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.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">
+
+ <android.support.design.widget.AppBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/AppTheme.AppBarOverlay">
+
+ <android.support.v7.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" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/content_main" />
+
+ <android.support.design.widget.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" />
+
+</android.support.design.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
new file mode 100644
index 00000000..110dc678
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.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" />
+
+</android.support.constraint.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
new file mode 100644
index 00000000..c4ad0980
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,10 @@
+<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
new file mode 100644
index 00000000..cde69bcc
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher.png
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
new file mode 100644
index 00000000..9a078e3e
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..c133a0cb
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher.png
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
new file mode 100644
index 00000000..efc028a6
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..bfa42f0e
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
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
new file mode 100644
index 00000000..3af2608a
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..324e72cd
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
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
new file mode 100644
index 00000000..9bec2e62
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..aee44e13
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
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
new file mode 100644
index 00000000..34947cd6
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
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
new file mode 100644
index 00000000..3ab3e9cb
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?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
new file mode 100644
index 00000000..59a0b0c4
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+<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
new file mode 100644
index 00000000..a94b2dfb
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<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
new file mode 100644
index 00000000..d4ea9ae7
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+<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 b/ui/kotlinx-coroutines-android/example-app/build.gradle
new file mode 100644
index 00000000..f512a873
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/build.gradle
@@ -0,0 +1,26 @@
+// 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.4.1'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/ui/kotlinx-coroutines-android/example-app/gradle.properties b/ui/kotlinx-coroutines-android/example-app/gradle.properties
new file mode 100644
index 00000000..8e119d71
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/gradle.properties
@@ -0,0 +1,23 @@
+# 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.coroutines=enable
+
+kotlin_version=1.3.50
+coroutines_version=1.3.1
+
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
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.jar
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
new file mode 100644
index 00000000..caf54fa2
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/ui/kotlinx-coroutines-android/example-app/gradlew b/ui/kotlinx-coroutines-android/example-app/gradlew
new file mode 100644
index 00000000..9d82f789
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# 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
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# 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
+
+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" ] ; 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/ui/kotlinx-coroutines-android/example-app/gradlew.bat b/ui/kotlinx-coroutines-android/example-app/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/gradlew.bat
@@ -0,0 +1,90 @@
+@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
+
+@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 DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@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 Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_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=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+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 b/ui/kotlinx-coroutines-android/example-app/settings.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/example-app/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/ui/kotlinx-coroutines-android/gradle/wrapper/gradle-wrapper.jar b/ui/kotlinx-coroutines-android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/gradle/wrapper/gradle-wrapper.jar
diff --git a/ui/kotlinx-coroutines-android/gradle/wrapper/gradle-wrapper.properties b/ui/kotlinx-coroutines-android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..d5aeee10
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Apr 08 03:07:42 CEST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/ui/kotlinx-coroutines-android/gradlew b/ui/kotlinx-coroutines-android/gradlew
new file mode 100644
index 00000000..cccdd3d5
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## 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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# 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/ui/kotlinx-coroutines-android/gradlew.bat b/ui/kotlinx-coroutines-android/gradlew.bat
new file mode 100644
index 00000000..f9553162
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/gradlew.bat
@@ -0,0 +1,84 @@
+@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 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=
+
+@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/package.list b/ui/kotlinx-coroutines-android/package.list
new file mode 100644
index 00000000..349cdcd8
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/package.list
@@ -0,0 +1,211 @@
+android
+android.accessibilityservice
+android.accounts
+android.animation
+android.annotation
+android.app
+android.app.admin
+android.app.assist
+android.app.backup
+android.app.job
+android.app.role
+android.app.slice
+android.app.usage
+android.appwidget
+android.bluetooth
+android.bluetooth.le
+android.companion
+android.content
+android.content.pm
+android.content.res
+android.database
+android.database.sqlite
+android.drm
+android.gesture
+android.graphics
+android.graphics.drawable
+android.graphics.drawable.shapes
+android.graphics.fonts
+android.graphics.pdf
+android.graphics.text
+android.hardware
+android.hardware.biometrics
+android.hardware.camera2
+android.hardware.camera2.params
+android.hardware.display
+android.hardware.fingerprint
+android.hardware.input
+android.hardware.usb
+android.icu.lang
+android.icu.math
+android.icu.text
+android.icu.util
+android.inputmethodservice
+android.location
+android.media
+android.media.audiofx
+android.media.browse
+android.media.effect
+android.media.midi
+android.media.projection
+android.media.session
+android.media.tv
+android.mtp
+android.net
+android.net.http
+android.net.nsd
+android.net.rtp
+android.net.sip
+android.net.ssl
+android.net.wifi
+android.net.wifi.aware
+android.net.wifi.hotspot2
+android.net.wifi.hotspot2.omadm
+android.net.wifi.hotspot2.pps
+android.net.wifi.p2p
+android.net.wifi.p2p.nsd
+android.net.wifi.rtt
+android.nfc
+android.nfc.cardemulation
+android.nfc.tech
+android.opengl
+android.os
+android.os.health
+android.os.storage
+android.os.strictmode
+android.preference
+android.print
+android.print.pdf
+android.printservice
+android.provider
+android.renderscript
+android.sax
+android.se.omapi
+android.security
+android.security.keystore
+android.service.autofill
+android.service.carrier
+android.service.chooser
+android.service.dreams
+android.service.media
+android.service.notification
+android.service.quicksettings
+android.service.restrictions
+android.service.textservice
+android.service.voice
+android.service.vr
+android.service.wallpaper
+android.speech
+android.speech.tts
+android.system
+android.telecom
+android.telephony
+android.telephony.cdma
+android.telephony.data
+android.telephony.emergency
+android.telephony.euicc
+android.telephony.gsm
+android.telephony.mbms
+android.test
+android.test.mock
+android.test.suitebuilder
+android.test.suitebuilder.annotation
+android.text
+android.text.format
+android.text.method
+android.text.style
+android.text.util
+android.transition
+android.util
+android.view
+android.view.accessibility
+android.view.animation
+android.view.autofill
+android.view.inputmethod
+android.view.inspector
+android.view.textclassifier
+android.view.textservice
+android.webkit
+android.widget
+dalvik.annotation
+dalvik.bytecode
+dalvik.system
+java.awt.font
+java.beans
+java.io
+java.lang
+java.lang.annotation
+java.lang.invoke
+java.lang.ref
+java.lang.reflect
+java.math
+java.net
+java.nio
+java.nio.channels
+java.nio.channels.spi
+java.nio.charset
+java.nio.charset.spi
+java.nio.file
+java.nio.file.attribute
+java.nio.file.spi
+java.security
+java.security.acl
+java.security.cert
+java.security.interfaces
+java.security.spec
+java.sql
+java.text
+java.time
+java.time.chrono
+java.time.format
+java.time.temporal
+java.time.zone
+java.util
+java.util.concurrent
+java.util.concurrent.atomic
+java.util.concurrent.locks
+java.util.function
+java.util.jar
+java.util.logging
+java.util.prefs
+java.util.regex
+java.util.stream
+java.util.zip
+javax.crypto
+javax.crypto.interfaces
+javax.crypto.spec
+javax.microedition.khronos.egl
+javax.microedition.khronos.opengles
+javax.net
+javax.net.ssl
+javax.security.auth
+javax.security.auth.callback
+javax.security.auth.login
+javax.security.auth.x500
+javax.security.cert
+javax.sql
+javax.xml
+javax.xml.datatype
+javax.xml.namespace
+javax.xml.parsers
+javax.xml.transform
+javax.xml.transform.dom
+javax.xml.transform.sax
+javax.xml.transform.stream
+javax.xml.validation
+javax.xml.xpath
+junit.framework
+junit.runner
+org.apache.http.conn
+org.apache.http.conn.scheme
+org.apache.http.conn.ssl
+org.apache.http.params
+org.json
+org.w3c.dom
+org.w3c.dom.ls
+org.xml.sax
+org.xml.sax.ext
+org.xml.sax.helpers
+org.xmlpull.v1
+org.xmlpull.v1.sax2
+
diff --git a/ui/kotlinx-coroutines-android/r8-test-common.pro b/ui/kotlinx-coroutines-android/r8-test-common.pro
new file mode 100644
index 00000000..03f36a82
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/r8-test-common.pro
@@ -0,0 +1,12 @@
+# Entry point for retaining MainDispatcherLoader which uses a ServiceLoader.
+-keep class kotlinx.coroutines.Dispatchers {
+ ** getMain();
+}
+
+# Entry point for retaining CoroutineExceptionHandlerImpl.handlers which uses a ServiceLoader.
+-keep class kotlinx.coroutines.CoroutineExceptionHandlerKt {
+ void handleCoroutineException(...);
+}
+
+# We are cheating a bit by not having android.jar on R8's library classpath. Ignore those warnings.
+-ignorewarnings \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/r8-test-rules-no-optim.pro b/ui/kotlinx-coroutines-android/r8-test-rules-no-optim.pro
new file mode 100644
index 00000000..d6bd4a42
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/r8-test-rules-no-optim.pro
@@ -0,0 +1,4 @@
+-include r8-test-common.pro
+
+# Include the shrinker config used by legacy versions of AGP and ProGuard
+-include resources/META-INF/com.android.tools/proguard/coroutines.pro
diff --git a/ui/kotlinx-coroutines-android/r8-test-rules.pro b/ui/kotlinx-coroutines-android/r8-test-rules.pro
new file mode 100644
index 00000000..2e7fdd8e
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/r8-test-rules.pro
@@ -0,0 +1,7 @@
+-include r8-test-common.pro
+
+# Ensure the custom, fast service loader implementation is removed. In the case of fast service
+# loader encountering an exception it falls back to regular ServiceLoader in a way that cannot be
+# optimized out by R8.
+-include resources/META-INF/com.android.tools/r8-from-1.6.0/coroutines.pro
+-checkdiscard class kotlinx.coroutines.internal.FastServiceLoader \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/proguard/coroutines.pro b/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/proguard/coroutines.pro
new file mode 100644
index 00000000..c7cd15fe
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/proguard/coroutines.pro
@@ -0,0 +1,5 @@
+# When editing this file, update the following files as well:
+# - META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro
+# - META-INF/proguard/coroutines.pro
+
+-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
diff --git a/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-from-1.6.0/coroutines.pro b/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-from-1.6.0/coroutines.pro
new file mode 100644
index 00000000..3c0b7e6a
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-from-1.6.0/coroutines.pro
@@ -0,0 +1,6 @@
+# Allow R8 to optimize away the FastServiceLoader.
+# Together with ServiceLoader optimization in R8
+# this results in direct instantiation when loading Dispatchers.Main
+-assumenosideeffects class kotlinx.coroutines.internal.MainDispatcherLoader {
+ boolean FAST_SERVICE_LOADER_ENABLED return false;
+} \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro b/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro
new file mode 100644
index 00000000..de1b70fc
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro
@@ -0,0 +1,5 @@
+# When editing this file, update the following files as well:
+# - META-INF/com.android.tools/proguard/coroutines.pro
+# - META-INF/proguard/coroutines.pro
+
+-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
diff --git a/ui/kotlinx-coroutines-android/resources/META-INF/proguard/coroutines.pro b/ui/kotlinx-coroutines-android/resources/META-INF/proguard/coroutines.pro
new file mode 100644
index 00000000..6c918d49
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/resources/META-INF/proguard/coroutines.pro
@@ -0,0 +1,7 @@
+# Files in this directory will be ignored starting with Android Gradle Plugin 3.6.0+
+
+# When editing this file, update the following files as well for AGP 3.6.0+:
+# - META-INF/com.android.tools/proguard/coroutines.pro
+# - META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro
+
+-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
diff --git a/ui/kotlinx-coroutines-android/resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler b/ui/kotlinx-coroutines-android/resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler
new file mode 100644
index 00000000..1471ed7a
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler
@@ -0,0 +1 @@
+kotlinx.coroutines.android.AndroidExceptionPreHandler \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory b/ui/kotlinx-coroutines-android/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
new file mode 100644
index 00000000..387be938
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
@@ -0,0 +1 @@
+kotlinx.coroutines.android.AndroidDispatcherFactory \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt
new file mode 100644
index 00000000..09443269
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-2018 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 android.support.annotation.*
+import kotlinx.coroutines.*
+import java.lang.reflect.*
+import kotlin.coroutines.*
+
+@Keep
+internal class AndroidExceptionPreHandler :
+ AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler, Function0<Method?> {
+
+ private val preHandler by lazy(this)
+
+ // Reflectively lookup pre-handler. Implement Function0 to avoid generating second class for lambda
+ override fun invoke(): Method? = try {
+ Thread::class.java.getDeclaredMethod("getUncaughtExceptionPreHandler").takeIf {
+ Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
+ }
+ } catch (e: Throwable) {
+ null /* not found */
+ }
+
+ override fun handleException(context: CoroutineContext, exception: Throwable) {
+ /*
+ * If we are on old SDK, then use Android's `Thread.getUncaughtExceptionPreHandler()` that ensures that
+ * an exception is logged before crashing the application.
+ *
+ * Since Android Pie default uncaught exception handler always ensures that exception is logged without interfering with
+ * pre-handler, so reflection hack is no longer needed.
+ *
+ * See https://android-review.googlesource.com/c/platform/frameworks/base/+/654578/
+ */
+ val thread = Thread.currentThread()
+ if (Build.VERSION.SDK_INT >= 28) {
+ thread.uncaughtExceptionHandler.uncaughtException(thread, exception)
+ } else {
+ (preHandler?.invoke(null) as? Thread.UncaughtExceptionHandler)
+ ?.uncaughtException(thread, exception)
+ }
+ }
+}
diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
new file mode 100644
index 00000000..8d4cecb0
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused")
+
+package kotlinx.coroutines.android
+
+import android.os.*
+import android.support.annotation.*
+import android.view.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import java.lang.reflect.*
+import kotlin.coroutines.*
+
+/**
+ * Dispatches execution onto Android [Handler].
+ *
+ * This class provides type-safety and a point for future extensions.
+ */
+public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay {
+ /**
+ * Returns dispatcher that executes coroutines immediately when it is already in the right context
+ * (current looper is the same as this handler's looper) without an additional [re-dispatch][CoroutineDispatcher.dispatch].
+ * This dispatcher does not use [Handler.post] when current looper is the same as looper of the handler.
+ *
+ * Immediate dispatcher is safe from stack overflows and in case of nested invocations forms event-loop similar to [Dispatchers.Unconfined].
+ * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation.
+ *
+ * Example of usage:
+ * ```
+ * suspend fun updateUiElement(val text: String) {
+ * /*
+ * * If it is known that updateUiElement can be invoked both from the Main thread and from other threads,
+ * * `immediate` dispatcher is used as a performance optimization to avoid unnecessary dispatch.
+ * *
+ * * In that case, when `updateUiElement` is invoked from the Main thread, `uiElement.text` will be
+ * * invoked immediately without any dispatching, otherwise, the `Dispatchers.Main` dispatch cycle via
+ * * `Handler.post` will be triggered.
+ * */
+ * withContext(Dispatchers.Main.immediate) {
+ * uiElement.text = text
+ * }
+ * // Do context-independent logic such as logging
+ * }
+ * ```
+ */
+ public abstract override val immediate: HandlerDispatcher
+}
+
+internal class AndroidDispatcherFactory : MainDispatcherFactory {
+
+ override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
+ HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")
+
+ 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]
+ * with an optional [name] for nicer debugging
+ */
+@JvmName("from") // this is for a nice Java API, see issue #255
+@JvmOverloads
+public fun Handler.asCoroutineDispatcher(name: String? = null): HandlerDispatcher =
+ HandlerContext(this, name)
+
+private const val MAX_DELAY = Long.MAX_VALUE / 2 // cannot delay for too long on Android
+
+@VisibleForTesting
+internal fun Looper.asHandler(async: Boolean): Handler {
+ // Async support was added in API 16.
+ if (!async || Build.VERSION.SDK_INT < 16) {
+ return Handler(this)
+ }
+
+ if (Build.VERSION.SDK_INT >= 28) {
+ // TODO compile against API 28 so this can be invoked without reflection.
+ val factoryMethod = Handler::class.java.getDeclaredMethod("createAsync", Looper::class.java)
+ return factoryMethod.invoke(null, this) as Handler
+ }
+
+ val constructor: Constructor<Handler>
+ try {
+ constructor = Handler::class.java.getDeclaredConstructor(Looper::class.java,
+ Handler.Callback::class.java, Boolean::class.javaPrimitiveType)
+ } catch (ignored: NoSuchMethodException) {
+ // Hidden constructor absent. Fall back to non-async constructor.
+ return Handler(this)
+ }
+ return constructor.newInstance(this, null, true)
+}
+
+@JvmField
+@Deprecated("Use Dispatchers.Main instead", level = DeprecationLevel.HIDDEN)
+internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main") }.getOrNull()
+
+/**
+ * Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler].
+ */
+internal class HandlerContext private constructor(
+ private val handler: Handler,
+ private val name: String?,
+ private val invokeImmediately: Boolean
+) : HandlerDispatcher(), Delay {
+ /**
+ * Creates [CoroutineDispatcher] for the given Android [handler].
+ *
+ * @param handler a handler.
+ * @param name an optional name for debugging.
+ */
+ public constructor(
+ handler: Handler,
+ name: String? = null
+ ) : this(handler, name, false)
+
+ @Volatile
+ private var _immediate: HandlerContext? = if (invokeImmediately) this else null
+
+ override val immediate: HandlerContext = _immediate ?:
+ HandlerContext(handler, name, true).also { _immediate = it }
+
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+ return !invokeImmediately || Looper.myLooper() != handler.looper
+ }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ handler.post(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) }
+ }
+
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
+ return object : DisposableHandle {
+ override fun dispose() {
+ handler.removeCallbacks(block)
+ }
+ }
+ }
+
+ override fun toString(): String =
+ if (name != null) {
+ if (invokeImmediately) "$name [immediate]" else name
+ } else {
+ handler.toString()
+ }
+
+ override fun equals(other: Any?): Boolean = other is HandlerContext && other.handler === handler
+ override fun hashCode(): Int = System.identityHashCode(handler)
+}
+
+@Volatile
+private var choreographer: Choreographer? = null
+
+/**
+ * Awaits the next animation frame and returns frame time in nanoseconds.
+ */
+public suspend fun awaitFrame(): Long {
+ // fast path when choreographer is already known
+ val choreographer = choreographer
+ if (choreographer != null) {
+ return suspendCancellableCoroutine { cont ->
+ postFrameCallback(choreographer, cont)
+ }
+ }
+ // post into looper thread thread to figure it out
+ return suspendCancellableCoroutine { cont ->
+ Dispatchers.Main.dispatch(EmptyCoroutineContext, Runnable {
+ updateChoreographerAndPostFrameCallback(cont)
+ })
+ }
+}
+
+private fun updateChoreographerAndPostFrameCallback(cont: CancellableContinuation<Long>) {
+ val choreographer = choreographer ?:
+ Choreographer.getInstance()!!.also { choreographer = it }
+ postFrameCallback(choreographer, cont)
+}
+
+private fun postFrameCallback(choreographer: Choreographer, cont: CancellableContinuation<Long>) {
+ choreographer.postFrameCallback { nanos ->
+ with(cont) { Dispatchers.Main.resumeUndispatched(nanos) }
+ }
+}
diff --git a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt
new file mode 100644
index 00000000..e006f004
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2016-2018 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.Test
+import org.junit.runner.*
+import org.robolectric.*
+import org.robolectric.Shadows.*
+import org.robolectric.annotation.*
+import org.robolectric.shadows.*
+import org.robolectric.util.*
+import kotlin.test.*
+
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest = Config.NONE, sdk = [28])
+class HandlerDispatcherTest : TestBase() {
+
+ /**
+ * Because [Dispatchers.Main] is a singleton, we cannot vary its initialization behavior. As a
+ * result we only test its behavior on the newest API level and assert that it uses async
+ * messages. We rely on the other tests to exercise the variance of the mechanism that the main
+ * dispatcher uses to ensure it has correct behavior on all API levels.
+ */
+ @Test
+ fun mainIsAsync() = runTest {
+ ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
+
+ val mainLooper = ShadowLooper.getShadowMainLooper()
+ mainLooper.pause()
+ val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
+
+ val job = launch(Dispatchers.Main) {
+ expect(2)
+ }
+
+ val message = mainMessageQueue.head
+ assertTrue(message.isAsynchronous)
+ job.join(mainLooper)
+ }
+
+ @Test
+ fun asyncMessagesApi14() = runTest {
+ ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 14)
+
+ val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
+
+ val mainLooper = ShadowLooper.getShadowMainLooper()
+ mainLooper.pause()
+ val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
+
+ val job = launch(main) {
+ expect(2)
+ }
+
+ val message = mainMessageQueue.head
+ assertFalse(message.isAsynchronous)
+ job.join(mainLooper)
+ }
+
+ @Test
+ fun asyncMessagesApi16() = runTest {
+ ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 16)
+
+ val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
+
+ val mainLooper = ShadowLooper.getShadowMainLooper()
+ mainLooper.pause()
+ val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
+
+ val job = launch(main) {
+ expect(2)
+ }
+
+ val message = mainMessageQueue.head
+ assertTrue(message.isAsynchronous)
+ job.join(mainLooper)
+ }
+
+ @Test
+ fun asyncMessagesApi28() = runTest {
+ ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
+
+ val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
+
+ val mainLooper = ShadowLooper.getShadowMainLooper()
+ mainLooper.pause()
+ val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
+
+ val job = launch(main) {
+ expect(2)
+ }
+
+ val message = mainMessageQueue.head
+ assertTrue(message.isAsynchronous)
+ job.join(mainLooper)
+ }
+
+ @Test
+ fun noAsyncMessagesIfNotRequested() = runTest {
+ ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
+
+ val main = Looper.getMainLooper().asHandler(async = false).asCoroutineDispatcher()
+
+ val mainLooper = ShadowLooper.getShadowMainLooper()
+ mainLooper.pause()
+ val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
+
+ val job = launch(main) {
+ expect(2)
+ }
+
+ val message = mainMessageQueue.head
+ assertFalse(message.isAsynchronous)
+ job.join(mainLooper)
+ }
+
+ @Test
+ fun testToString() {
+ ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
+ val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher("testName")
+ assertEquals("testName", main.toString())
+ assertEquals("testName [immediate]", main.immediate.toString())
+ assertEquals("testName [immediate]", main.immediate.immediate.toString())
+ }
+
+ private suspend fun Job.join(mainLooper: ShadowLooper) {
+ expect(1)
+ mainLooper.unPause()
+ join()
+ finish(3)
+ }
+
+ // TODO compile against API 23+ so this can be invoked without reflection.
+ private val Looper.queue: MessageQueue
+ get() = Looper::class.java.getDeclaredMethod("getQueue").invoke(this) as MessageQueue
+
+ // TODO compile against API 22+ so this can be invoked without reflection.
+ private val Message.isAsynchronous: Boolean
+ get() = Message::class.java.getDeclaredMethod("isAsynchronous").invoke(this) as Boolean
+}
diff --git a/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt b/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt
new file mode 100644
index 00000000..2d2281bd
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.android
+
+import org.jf.dexlib2.*
+import org.junit.Test
+import java.io.*
+import java.util.stream.*
+import kotlin.test.*
+
+class R8ServiceLoaderOptimizationTest {
+ private val r8Dex = File(System.getProperty("dexPath")!!).asDexFile()
+ private val r8DexNoOptim = File(System.getProperty("noOptimDexPath")!!).asDexFile()
+
+ @Test
+ fun noServiceLoaderCalls() {
+ val serviceLoaderInvocations = r8Dex.types.any {
+ it.type == "Ljava/util/ServiceLoader;"
+ }
+ assertEquals(
+ false,
+ serviceLoaderInvocations,
+ "References to the ServiceLoader class were found in the resulting DEX."
+ )
+ }
+
+ @Test
+ fun androidDispatcherIsKept() {
+ val hasAndroidDispatcher = r8DexNoOptim.classes.any {
+ it.type == "Lkotlinx/coroutines/android/AndroidDispatcherFactory;"
+ }
+
+ assertEquals(true, hasAndroidDispatcher)
+ }
+
+ @Test
+ fun noOptimRulesMatch() {
+ val paths = listOf(
+ "META-INF/com.android.tools/proguard/coroutines.pro",
+ "META-INF/proguard/coroutines.pro",
+ "META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro"
+ )
+ paths.associateWith { path ->
+ val ruleSet = javaClass.classLoader.getResourceAsStream(path)!!.bufferedReader().lines().filter { line ->
+ line.isNotBlank() && !line.startsWith("#")
+ }.collect(Collectors.toSet())
+ ruleSet
+ }.asSequence().reduce { acc, entry ->
+ assertEquals(
+ acc.value,
+ entry.value,
+ "Rule sets between ${acc.key} and ${entry.key} don't match."
+ )
+ entry
+ }
+ }
+}
+
+private fun File.asDexFile() = DexFileFactory.loadDexFile(this, null)
diff --git a/ui/kotlinx-coroutines-javafx/README.md b/ui/kotlinx-coroutines-javafx/README.md
new file mode 100644
index 00000000..88f5bc2f
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/README.md
@@ -0,0 +1,10 @@
+# Module kotlinx-coroutines-javafx
+
+Provides `Dispatchers.JavaFx` context and `Dispatchers.Main` implementation for JavaFX UI applications.
+
+Read [Guide to UI programming with coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md)
+for tutorial on this module.
+
+# Package kotlinx.coroutines.javafx
+
+Provides `Dispatchers.JavaFx` context and `Dispatchers.Main` implementation for JavaFX UI applications.
diff --git a/ui/kotlinx-coroutines-javafx/build.gradle b/ui/kotlinx-coroutines-javafx/build.gradle
new file mode 100644
index 00000000..3b17101f
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/build.gradle
@@ -0,0 +1,4 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
diff --git a/ui/kotlinx-coroutines-javafx/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory b/ui/kotlinx-coroutines-javafx/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
new file mode 100644
index 00000000..6dd7e156
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
@@ -0,0 +1 @@
+kotlinx.coroutines.javafx.JavaFxDispatcherFactory
diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
new file mode 100644
index 00000000..eb45b3eb
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.javafx
+
+import javafx.animation.*
+import javafx.application.*
+import javafx.event.*
+import javafx.util.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.javafx.JavaFx.delay
+import java.lang.reflect.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+/**
+ * Dispatches execution onto JavaFx application thread and provides native [delay] support.
+ */
+@Suppress("unused")
+public val Dispatchers.JavaFx: JavaFxDispatcher
+ get() = kotlinx.coroutines.javafx.JavaFx
+
+/**
+ * Dispatcher for JavaFx application thread with support for [awaitPulse].
+ *
+ * This class provides type-safety and a point for future extensions.
+ */
+public sealed class JavaFxDispatcher : MainCoroutineDispatcher(), Delay {
+
+ /** @suppress */
+ override fun dispatch(context: CoroutineContext, block: Runnable) = Platform.runLater(block)
+
+ /** @suppress */
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ val timeline = schedule(timeMillis, TimeUnit.MILLISECONDS, EventHandler {
+ with(continuation) { resumeUndispatched(Unit) }
+ })
+ continuation.invokeOnCancellation { timeline.stop() }
+ }
+
+ /** @suppress */
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val timeline = schedule(timeMillis, TimeUnit.MILLISECONDS, EventHandler {
+ block.run()
+ })
+ return object : DisposableHandle {
+ override fun dispose() {
+ timeline.stop()
+ }
+ }
+ }
+
+ private fun schedule(time: Long, unit: TimeUnit, handler: EventHandler<ActionEvent>): Timeline =
+ Timeline(KeyFrame(Duration.millis(unit.toMillis(time).toDouble()), handler)).apply { play() }
+}
+
+internal class JavaFxDispatcherFactory : MainDispatcherFactory {
+ override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher = JavaFx
+
+ override val loadPriority: Int
+ get() = 1 // Swing has 0
+}
+
+private object ImmediateJavaFxDispatcher : JavaFxDispatcher() {
+ override val immediate: MainCoroutineDispatcher
+ get() = this
+
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean = !Platform.isFxApplicationThread()
+
+ override fun toString() = "JavaFx [immediate]"
+}
+
+/**
+ * Dispatches execution onto JavaFx application thread and provides native [delay] support.
+ */
+internal object JavaFx : JavaFxDispatcher() {
+ init {
+ // :kludge: to make sure Toolkit is initialized if we use JavaFx dispatcher outside of JavaFx app
+ initPlatform()
+ }
+
+ override val immediate: MainCoroutineDispatcher
+ get() = ImmediateJavaFxDispatcher
+
+ override fun toString() = "JavaFx"
+}
+
+private val pulseTimer by lazy {
+ PulseTimer().apply { start() }
+}
+
+/**
+ * Suspends coroutine until next JavaFx pulse and returns time of the pulse on resumption.
+ * If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
+ * immediately resumes with [CancellationException][kotlinx.coroutines.CancellationException].
+ */
+public suspend fun awaitPulse(): Long = suspendCancellableCoroutine { cont ->
+ pulseTimer.onNext(cont)
+}
+
+private class PulseTimer : AnimationTimer() {
+ val next = CopyOnWriteArrayList<CancellableContinuation<Long>>()
+
+ override fun handle(now: Long) {
+ val cur = next.toTypedArray()
+ next.clear()
+ for (cont in cur)
+ with (cont) { JavaFx.resumeUndispatched(now) }
+ }
+
+ fun onNext(cont: CancellableContinuation<Long>) {
+ next += cont
+ }
+}
+
+internal fun initPlatform(): Boolean {
+ /*
+ * Try to instantiate JavaFx platform in a way which works
+ * both on Java 8 and Java 11 and does not produce "illegal reflective access":
+ *
+ * 1) Try to invoke javafx.application.Platform.startup if this class is
+ * present in a classpath.
+ * 2) If it is not successful and does not because it is already started,
+ * fallback to PlatformImpl.
+ *
+ * Ignore exception anyway in case of unexpected changes in API, in that case
+ * user will have to instantiate it manually.
+ */
+ val runnable = Runnable {}
+ return runCatching {
+ // Invoke public API if it is present
+ Class.forName("javafx.application.Platform")
+ .getMethod("startup", java.lang.Runnable::class.java)
+ .invoke(null, runnable)
+ }.recoverCatching { exception ->
+ // Recover -> check re-initialization
+ val cause = exception.cause
+ if (exception is InvocationTargetException && cause is IllegalStateException
+ && "Toolkit already initialized" == cause.message) {
+ // Toolkit is already initialized -> success, return
+ Unit
+ } else { // Fallback to Java 8 API
+ Class.forName("com.sun.javafx.application.PlatformImpl")
+ .getMethod("startup", java.lang.Runnable::class.java)
+ .invoke(null, runnable)
+ }
+ }.isSuccess
+}
diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxTest.kt
new file mode 100644
index 00000000..178f961a
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/JavaFxTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.javafx
+
+import javafx.application.*
+import kotlinx.coroutines.*
+import org.junit.*
+
+class JavaFxTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("JavaFX Application Thread", "Thread-", "QuantumRenderer-", "InvokeLaterDispatcher")
+ }
+
+ @Test
+ fun testDelay() {
+ if (!initPlatform()) {
+ println("Skipping JavaFxTest in headless environment")
+ return // ignore test in headless environments
+ }
+
+ runBlocking {
+ expect(1)
+ val job = launch(Dispatchers.JavaFx) {
+ check(Platform.isFxApplicationThread())
+ expect(2)
+ delay(100)
+ check(Platform.isFxApplicationThread())
+ expect(3)
+ }
+ job.join()
+ finish(4)
+ }
+ }
+} \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-javafx/test/examples/FxExampleApp.kt b/ui/kotlinx-coroutines-javafx/test/examples/FxExampleApp.kt
new file mode 100644
index 00000000..f2b7fe06
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/examples/FxExampleApp.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package examples
+
+import javafx.application.*
+import javafx.scene.*
+import javafx.scene.control.*
+import javafx.scene.layout.*
+import javafx.scene.paint.*
+import javafx.scene.shape.*
+import javafx.stage.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.javafx.*
+import java.text.*
+import java.util.*
+import kotlin.coroutines.*
+
+fun main(args: Array<String>) {
+ Application.launch(FxTestApp::class.java, *args)
+}
+
+fun log(msg: String) = println("${SimpleDateFormat("yyyyMMdd-HHmmss.sss").format(Date())} [${Thread.currentThread().name}] $msg")
+
+class FxTestApp : Application(), CoroutineScope {
+ val buttons = FlowPane().apply {
+ children += Button("Rect").apply {
+ setOnAction { doRect() }
+ }
+ children += Button("Circle").apply {
+ setOnAction { doCircle() }
+ }
+ children += Button("Clear").apply {
+ setOnAction { doClear() }
+ }
+ }
+
+ val root = Pane().apply {
+ children += buttons
+ }
+
+ val scene = Scene(root, 600.0, 400.0)
+
+ override fun start(stage: Stage) {
+ stage.title = "Hello world!"
+ stage.scene = scene
+ stage.show()
+ }
+
+ val random = Random()
+ var animationIndex = 0
+ var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = Dispatchers.JavaFx + job
+
+ private fun animation(node: Node, block: suspend CoroutineScope.() -> Unit) {
+ root.children += node
+ launch(block = block).also {
+ it.invokeOnCompletion { root.children -= node }
+ }
+ }
+
+ fun doRect() {
+ val node = Rectangle(20.0, 20.0).apply {
+ fill = Color.RED
+ }
+ val index = ++animationIndex
+ val speed = 5.0
+ animation(node) {
+ log("Started new 'rect' coroutine #$index")
+ var vx = speed
+ var vy = speed
+ var counter = 0
+ while (true) {
+ awaitPulse()
+ node.x += vx
+ node.y += vy
+ val xRange = 0.0 .. scene.width - node.width
+ val yRange = 0.0 .. scene.height - node.height
+ if (node.x !in xRange ) {
+ node.x = node.x.coerceIn(xRange)
+ vx = -vx
+ }
+ if (node.y !in yRange) {
+ node.y = node.y.coerceIn(yRange)
+ vy = -vy
+ }
+ if (counter++ > 100) {
+ counter = 0
+ delay(1000) // pause a bit
+ log("Delayed #$index for a while, resume and turn")
+ val t = vx
+ vx = vy
+ vy = -t
+ }
+ }
+ }
+ }
+
+ fun doCircle() {
+ val node = Circle(20.0).apply {
+ fill = Color.BLUE
+ }
+ val index = ++animationIndex
+ val acceleration = 0.1
+ val maxSpeed = 5.0
+ animation(node) {
+ log("Started new 'circle' coroutine #$index")
+ var sx = random.nextDouble() * maxSpeed
+ var sy = random.nextDouble() * maxSpeed
+ while (true) {
+ awaitPulse()
+ val dx = root.width / 2 - node.translateX
+ val dy = root.height / 2 - node.translateY
+ val dn = Math.sqrt(dx * dx + dy * dy)
+ sx += dx / dn * acceleration
+ sy += dy / dn * acceleration
+ val sn = Math.sqrt(sx * sx + sy * sy)
+ val trim = sn.coerceAtMost(maxSpeed)
+ sx = sx / sn * trim
+ sy = sy / sn * trim
+ node.translateX += sx
+ node.translateY += sy
+ }
+ }
+ }
+
+ fun doClear() {
+ job.cancel()
+ job = Job()
+ }
+} \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt
new file mode 100644
index 00000000..58da16da
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.actor01
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ fab.onClick { // start coroutine when the circle is clicked
+ for (i in 10 downTo 1) { // countdown from 10 to 1
+ hello.text = "Countdown $i ..." // update text
+ delay(500) // wait half a second
+ }
+ hello.text = "Done!"
+ }
+}
+
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ onMouseClicked = EventHandler { event ->
+ GlobalScope.launch(Dispatchers.Main) {
+ action(event)
+ }
+ }
+}
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
new file mode 100644
index 00000000..a7be2f51
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.actor02
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ fab.onClick { // start coroutine when the circle is clicked
+ for (i in 10 downTo 1) { // countdown from 10 to 1
+ hello.text = "Countdown $i ..." // update text
+ delay(500) // wait half a second
+ }
+ hello.text = "Done!"
+ }
+}
+
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ // launch one actor to handle all events on this node
+ val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main) {
+ for (event in channel) action(event) // pass event to action
+ }
+ // install a listener to offer events to this actor
+ onMouseClicked = EventHandler { event ->
+ eventActor.offer(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
new file mode 100644
index 00000000..c2926afc
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.actor03
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ fab.onClick { // start coroutine when the circle is clicked
+ for (i in 10 downTo 1) { // countdown from 10 to 1
+ hello.text = "Countdown $i ..." // update text
+ delay(500) // wait half a second
+ }
+ hello.text = "Done!"
+ }
+}
+
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ // launch one actor to handle all events on this node
+ 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
+ onMouseClicked = EventHandler { event ->
+ eventActor.offer(event)
+ }
+}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt
new file mode 100644
index 00000000..2965c04c
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.advanced01
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ fab.onMouseClicked = EventHandler {
+ println("Before launch")
+ GlobalScope.launch(Dispatchers.Main) {
+ println("Inside coroutine")
+ delay(100)
+ println("After delay")
+ }
+ println("After launch")
+ }
+}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt
new file mode 100644
index 00000000..fa27d184
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.advanced02
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ fab.onMouseClicked = EventHandler {
+ println("Before launch")
+ GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { // <--- Notice this change
+ println("Inside coroutine")
+ delay(100) // <--- And this is where coroutine suspends
+ println("After delay")
+ }
+ println("After launch")
+ }
+}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt
new file mode 100644
index 00000000..d7ea5999
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.basic01
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ // placeholder
+}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt
new file mode 100644
index 00000000..45967e73
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.basic02
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread
+ for (i in 10 downTo 1) { // countdown from 10 to 1
+ hello.text = "Countdown $i ..." // update text
+ delay(500) // wait half a second
+ }
+ hello.text = "Done!"
+ }
+}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt
new file mode 100644
index 00000000..06106605
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.basic03
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ val job = GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread
+ for (i in 10 downTo 1) { // countdown from 10 to 1
+ hello.text = "Countdown $i ..." // update text
+ delay(500) // wait half a second
+ }
+ hello.text = "Done!"
+ }
+ fab.onMouseClicked = EventHandler { job.cancel() } // cancel coroutine on click
+}
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
new file mode 100644
index 00000000..2ff5a2fb
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.blocking01
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) {
+ for (event in channel) action(event) // pass event to action
+ }
+ onMouseClicked = EventHandler { event ->
+ eventActor.offer(event)
+ }
+}
+
+fun fib(x: Int): Int =
+ if (x <= 1) x else fib(x - 1) + fib(x - 2)
+
+fun setup(hello: Text, fab: Circle) {
+ var result = "none" // the last result
+ // counting animation
+ GlobalScope.launch(Dispatchers.Main) {
+ var counter = 0
+ while (true) {
+ hello.text = "${++counter}: $result"
+ delay(100) // update the text every 100ms
+ }
+ }
+ // compute the next fibonacci number of each click
+ var x = 1
+ fab.onClick {
+ result = "fib($x) = ${fib(x)}"
+ x++
+ }
+}
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
new file mode 100644
index 00000000..6a87025d
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.blocking02
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) {
+ for (event in channel) action(event) // pass event to action
+ }
+ onMouseClicked = EventHandler { event ->
+ eventActor.offer(event)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ var result = "none" // the last result
+ // counting animation
+ GlobalScope.launch(Dispatchers.Main) {
+ var counter = 0
+ while (true) {
+ hello.text = "${++counter}: $result"
+ delay(100) // update the text every 100ms
+ }
+ }
+ // compute next fibonacci number of each click
+ var x = 1
+ fab.onClick {
+ result = "fib($x) = ${fib(x)}"
+ x++
+ }
+}
+
+suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) {
+ if (x <= 1) x else fib(x - 1) + fib(x - 2)
+}
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
new file mode 100644
index 00000000..1388e635
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
+package kotlinx.coroutines.javafx.guide.blocking03
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.javafx.JavaFx as Main
+import javafx.application.Application
+import javafx.event.EventHandler
+import javafx.geometry.*
+import javafx.scene.*
+import javafx.scene.input.MouseEvent
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.shape.Circle
+import javafx.scene.text.Text
+import javafx.stage.Stage
+
+fun main(args: Array<String>) {
+ Application.launch(ExampleApp::class.java, *args)
+}
+
+class ExampleApp : Application() {
+ val hello = Text("Hello World!").apply {
+ fill = Color.valueOf("#C0C0C0")
+ }
+
+ val fab = Circle(20.0, Color.valueOf("#FF4081"))
+
+ val root = StackPane().apply {
+ children += hello
+ children += fab
+ StackPane.setAlignment(hello, Pos.CENTER)
+ StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
+ StackPane.setMargin(fab, Insets(15.0))
+ }
+
+ val scene = Scene(root, 240.0, 380.0).apply {
+ fill = Color.valueOf("#303030")
+ }
+
+ override fun start(stage: Stage) {
+ stage.title = "Example"
+ stage.scene = scene
+ stage.show()
+ setup(hello, fab)
+ }
+}
+
+fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
+ val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) {
+ for (event in channel) action(event) // pass event to action
+ }
+ onMouseClicked = EventHandler { event ->
+ eventActor.offer(event)
+ }
+}
+
+fun setup(hello: Text, fab: Circle) {
+ var result = "none" // the last result
+ // counting animation
+ GlobalScope.launch(Dispatchers.Main) {
+ var counter = 0
+ while (true) {
+ hello.text = "${++counter}: $result"
+ delay(100) // update the text every 100ms
+ }
+ }
+ // compute next fibonacci number of each click
+ var x = 1
+ fab.onClick {
+ result = "fib($x) = ${fib(x)}"
+ x++
+ }
+}
+
+suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) {
+ fibBlocking(x)
+}
+
+fun fibBlocking(x: Int): Int =
+ if (x <= 1) x else fibBlocking(x - 1) + fibBlocking(x - 2)
diff --git a/ui/kotlinx-coroutines-swing/README.md b/ui/kotlinx-coroutines-swing/README.md
new file mode 100644
index 00000000..caf6b4e3
--- /dev/null
+++ b/ui/kotlinx-coroutines-swing/README.md
@@ -0,0 +1,10 @@
+# Module kotlinx-coroutines-swing
+
+Provides `Dispatchers.Swing` context and `Dispatchers.Main` implementation for Swing UI applications.
+
+Read [Guide to UI programming with coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md)
+for tutorial on this module.
+
+# Package kotlinx.coroutines.swing
+
+Provides `Dispatchers.Swing` context and `Dispatchers.Main` implementation for Swing UI applications.
diff --git a/ui/kotlinx-coroutines-swing/build.gradle b/ui/kotlinx-coroutines-swing/build.gradle
new file mode 100644
index 00000000..31761abe
--- /dev/null
+++ b/ui/kotlinx-coroutines-swing/build.gradle
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+dependencies {
+ testCompile project(':kotlinx-coroutines-jdk8')
+} \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-swing/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory b/ui/kotlinx-coroutines-swing/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
new file mode 100644
index 00000000..d1613a4f
--- /dev/null
+++ b/ui/kotlinx-coroutines-swing/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
@@ -0,0 +1 @@
+kotlinx.coroutines.swing.SwingDispatcherFactory
diff --git a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
new file mode 100644
index 00000000..81176da3
--- /dev/null
+++ b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.swing
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import java.awt.event.*
+import java.util.concurrent.*
+import javax.swing.*
+import kotlin.coroutines.*
+
+/**
+ * Dispatches execution onto Swing event dispatching thread and provides native [delay] support.
+ */
+@Suppress("unused")
+public val Dispatchers.Swing : SwingDispatcher
+ get() = kotlinx.coroutines.swing.Swing
+
+/**
+ * Dispatcher for Swing event dispatching thread.
+ *
+ * This class provides type-safety and a point for future extensions.
+ */
+public sealed class SwingDispatcher : MainCoroutineDispatcher(), Delay {
+ /** @suppress */
+ override fun dispatch(context: CoroutineContext, block: Runnable) = SwingUtilities.invokeLater(block)
+
+ /** @suppress */
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ val timer = schedule(timeMillis, TimeUnit.MILLISECONDS, ActionListener {
+ with(continuation) { resumeUndispatched(Unit) }
+ })
+ continuation.invokeOnCancellation { timer.stop() }
+ }
+
+ /** @suppress */
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val timer = schedule(timeMillis, TimeUnit.MILLISECONDS, ActionListener {
+ block.run()
+ })
+ return object : DisposableHandle {
+ override fun dispose() {
+ timer.stop()
+ }
+ }
+ }
+
+ private fun schedule(time: Long, unit: TimeUnit, action: ActionListener): Timer =
+ Timer(unit.toMillis(time).coerceAtMost(Int.MAX_VALUE.toLong()).toInt(), action).apply {
+ isRepeats = false
+ start()
+ }
+}
+
+internal class SwingDispatcherFactory : MainDispatcherFactory {
+ override val loadPriority: Int
+ get() = 0
+
+ override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher = Swing
+}
+
+private object ImmediateSwingDispatcher : SwingDispatcher() {
+ override val immediate: MainCoroutineDispatcher
+ get() = this
+
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean = !SwingUtilities.isEventDispatchThread()
+
+ override fun toString() = "Swing [immediate]"
+}
+
+/**
+ * Dispatches execution onto Swing event dispatching thread and provides native [delay] support.
+ */
+internal object Swing : SwingDispatcher() {
+ override val immediate: MainCoroutineDispatcher
+ get() = ImmediateSwingDispatcher
+
+ override fun toString() = "Swing"
+}
diff --git a/ui/kotlinx-coroutines-swing/test/SwingTest.kt b/ui/kotlinx-coroutines-swing/test/SwingTest.kt
new file mode 100644
index 00000000..f6cc43f5
--- /dev/null
+++ b/ui/kotlinx-coroutines-swing/test/SwingTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.swing
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import javax.swing.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class SwingTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("AWT-EventQueue-")
+ }
+
+ @Test
+ fun testDelay() = runBlocking {
+ expect(1)
+ SwingUtilities.invokeLater { expect(2) }
+ val job = launch(Dispatchers.Swing) {
+ check(SwingUtilities.isEventDispatchThread())
+ expect(3)
+ SwingUtilities.invokeLater { expect(4) }
+ delay(100)
+ check(SwingUtilities.isEventDispatchThread())
+ expect(5)
+ }
+ job.join()
+ finish(6)
+ }
+
+ private class SwingComponent(coroutineContext: CoroutineContext = EmptyCoroutineContext) :
+ CoroutineScope by MainScope() + coroutineContext
+ {
+ public var executed = false
+ fun testLaunch(): Job = launch {
+ check(SwingUtilities.isEventDispatchThread())
+ executed = true
+ }
+ fun testFailure(): Job = launch {
+ check(SwingUtilities.isEventDispatchThread())
+ throw TestException()
+ }
+ fun testCancellation() : Job = launch(start = CoroutineStart.ATOMIC) {
+ check(SwingUtilities.isEventDispatchThread())
+ delay(Long.MAX_VALUE)
+ }
+ }
+
+ @Test
+ fun testLaunchInMainScope() = runTest {
+ val component = SwingComponent()
+ val job = component.testLaunch()
+ job.join()
+ assertTrue(component.executed)
+ component.cancel()
+ component.coroutineContext[Job]!!.join()
+ }
+
+ @Test
+ fun testFailureInMainScope() = runTest {
+ var exception: Throwable? = null
+ val component = SwingComponent(CoroutineExceptionHandler { ctx, e -> exception = e})
+ val job = component.testFailure()
+ job.join()
+ assertTrue(exception!! is TestException)
+ component.cancel()
+ join(component)
+ }
+
+ @Test
+ fun testCancellationInMainScope() = runTest {
+ val component = SwingComponent()
+ component.cancel()
+ component.testCancellation().join()
+ join(component)
+ }
+
+ private suspend fun join(component: SwingTest.SwingComponent) {
+ component.coroutineContext[Job]!!.join()
+ }
+} \ No newline at end of file
diff --git a/ui/kotlinx-coroutines-swing/test/examples/SwingExampleApp.kt b/ui/kotlinx-coroutines-swing/test/examples/SwingExampleApp.kt
new file mode 100644
index 00000000..7c396bf9
--- /dev/null
+++ b/ui/kotlinx-coroutines-swing/test/examples/SwingExampleApp.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.future.*
+import kotlinx.coroutines.swing.*
+import java.awt.*
+import java.util.concurrent.*
+import javax.swing.*
+
+private fun createAndShowGUI() {
+ val frame = JFrame("Async UI example")
+ frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
+
+ val jProgressBar = JProgressBar(0, 100).apply {
+ value = 0
+ isStringPainted = true
+ }
+
+ val jTextArea = JTextArea(11, 10)
+ jTextArea.margin = Insets(5, 5, 5, 5)
+ jTextArea.isEditable = false
+
+ val panel = JPanel()
+
+ panel.add(jProgressBar)
+ panel.add(jTextArea)
+
+ frame.contentPane.add(panel)
+ frame.pack()
+ frame.isVisible = true
+
+ GlobalScope.launch(Dispatchers.Swing) {
+ for (i in 1..10) {
+ // 'append' method and consequent 'jProgressBar.setValue' are called
+ // within Swing event dispatch thread
+ jTextArea.append(
+ startLongAsyncOperation(i).await()
+ )
+ jProgressBar.value = i * 10
+ }
+ }
+}
+
+private fun startLongAsyncOperation(v: Int) =
+ CompletableFuture.supplyAsync {
+ Thread.sleep(1000)
+ "Message: $v\n"
+ }
+
+fun main(args: Array<String>) {
+ SwingUtilities.invokeLater(::createAndShowGUI)
+}
diff --git a/ui/kotlinx-coroutines-swing/test/examples/swing-example.kt b/ui/kotlinx-coroutines-swing/test/examples/swing-example.kt
new file mode 100644
index 00000000..cadb4681
--- /dev/null
+++ b/ui/kotlinx-coroutines-swing/test/examples/swing-example.kt
@@ -0,0 +1,39 @@
+/*
+ * 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
+ }
+}
+
diff --git a/ui/ui-example-android.png b/ui/ui-example-android.png
new file mode 100644
index 00000000..78ed5afd
--- /dev/null
+++ b/ui/ui-example-android.png
Binary files differ
diff --git a/ui/ui-example-javafx.png b/ui/ui-example-javafx.png
new file mode 100644
index 00000000..60ded704
--- /dev/null
+++ b/ui/ui-example-javafx.png
Binary files differ