Merge tag 'android-13.0.0_r32' into int/13/fp3

Android 13.0.0 release 32

* tag 'android-13.0.0_r32':
  Merge tag '1.6.4' into kotlinx.coroutines-upgrade
  Statically include kotlinx_coroutines into kotlinx_coroutines_android
  Update sdk_version of kotlinx-coroutines
  Update arguments for kotlin 1.7.0

Change-Id: I6d8fb58d7671c4825ed459df060f461f5d934200
diff --git a/.gitignore b/.gitignore
index 52843ca..36de0e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@
 out
 target
 local.properties
+/kotlin-js-store
diff --git a/Android.bp b/Android.bp
index 5225d8c..795e5b0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,5 +1,6 @@
 package {
     default_applicable_licenses: ["external_kotlinx.coroutines_license"],
+    default_visibility: ["//visibility:private"],
 }
 
 // Added automatically by a large-scale-change that took the approach of
@@ -35,15 +36,16 @@
     ],
 }
 
-java_library {
-    name: "kotlinx_coroutines",
-    host_supported: true,
-    // This should be "core_current", but that causes nullability issues
-    // for returned platform types that are explicitly marked @Nullable in
-    // SDK 29 and current.
-    sdk_version: "28",
+// Upstream compiles this lib against the JVM bootclasspath; compiling against the Android
+// bootclasspath will fail. Work around this by defining this as a java_library_host, and use
+// java_host_for_device to expose it to Android targets.
+java_library_host {
+    name: "kotlinx_coroutines-host",
     srcs: ["kotlinx-coroutines-core/jvm/src/**/*.kt"],
-    common_srcs: ["kotlinx-coroutines-core/common/src/**/*.kt"],
+    common_srcs: [
+        "kotlinx-coroutines-core/common/src/**/*.kt",
+        "kotlinx-coroutines-core/concurrent/src/**/*.kt",
+    ],
     exclude_srcs: [
         "kotlinx-coroutines-core/jvm/src/debug/**/*.kt",
         "kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt",
@@ -52,94 +54,151 @@
     static_libs: [
         "kotlinx_atomicfu",
     ],
+    libs: [
+        "annotations", // for android.annotation.SuppressLint
+        "kotlinx-coroutines-android-annotation-stubs",
+    ],
     kotlincflags: [
         "-Xmulti-platform",
-        "-Xuse-experimental=kotlin.ExperimentalMultiplatform",
-        "-Xuse-experimental=kotlin.Experimental",
-        "-Xuse-experimental=kotlin.experimental.ExperimentalTypeInference",
-        "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
-        "-Xuse-experimental=kotlinx.coroutines.FlowPreview",
-        "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
-        "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
+        "-opt-in=kotlin.RequiresOptIn",
+        "-opt-in=kotlin.experimental.ExperimentalTypeInference",
+        "-opt-in=kotlin.ExperimentalMultiplatform",
+        "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi",
+        "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+        "-opt-in=kotlinx.coroutines.ObsoleteCoroutinesApi",
+        "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
+        "-opt-in=kotlinx.coroutines.FlowPreview",
     ],
     apex_available: [
         "//apex_available:platform",
-        "//apex_available:anyapex"
+        "//apex_available:anyapex",
     ],
 }
 
+// Expose the host library to Android targets. This is generally an unsafe operation; in using
+// this, we are asserting that any host-only code will never be evaluated at runtime on Android.
+// If we're wrong, we will see runtime exceptions.
+java_host_for_device {
+    name: "kotlinx_coroutines-device",
+    libs: ["kotlinx_coroutines-host"],
+}
+
+// Combine host and Android libs back into a single target.
+java_library {
+    name: "kotlinx_coroutines",
+    host_supported: true,
+    sdk_version: "core_current",
+    min_sdk_version: "28",
+    target: {
+        host: {
+            static_libs: ["kotlinx_coroutines-host"],
+        },
+        android: {
+            static_libs: ["kotlinx_coroutines-device"],
+        },
+    },
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    visibility: ["//visibility:public"],
+}
+
 java_library {
     name: "kotlinx_coroutines_android",
-    sdk_version: "28",
+    sdk_version: "current",
+    min_sdk_version: "28",
     srcs: ["ui/kotlinx-coroutines-android/src/**/*.kt"],
     java_resource_dirs: ["ui/kotlinx-coroutines-android/resources"],
     kotlincflags: [
-        "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
-        "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
+        "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
+        "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+    ],
+    static_libs: [
+        "kotlinx_coroutines",
     ],
     libs: [
-        "kotlinx_coroutines",
         "androidx.annotation_annotation",
     ],
     apex_available: [
         "//apex_available:platform",
-        "//apex_available:anyapex"
+        "//apex_available:anyapex",
     ],
+    visibility: ["//visibility:public"],
 }
 
 java_library {
     name: "kotlinx_coroutines_test",
     host_supported: true,
-    srcs: ["kotlinx-coroutines-test/src/**/*.kt"],
-    java_resource_dirs: ["kotlinx-coroutines-test/resources"],
+    srcs: ["kotlinx-coroutines-test/jvm/src/**/*.kt"],
+    common_srcs: ["kotlinx-coroutines-test/common/src/**/*.kt"],
+    java_resource_dirs: ["kotlinx-coroutines-test/jvm/resources"],
     kotlincflags: [
-        "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
-        "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
+        "-Xmulti-platform",
+        "-opt-in=kotlin.ExperimentalMultiplatform",
+        "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
+        "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
     ],
-    libs: [
-        "kotlinx_coroutines",
-    ],
+    libs: ["kotlinx_coroutines"],
     apex_available: [
         "//apex_available:platform",
-        "//apex_available:anyapex"
+        "//apex_available:anyapex",
     ],
+    visibility: ["//visibility:public"],
 }
 
-// Temporary aliases for kotlinx-coroutines-core, kotlinx-coroutines-core-jvm and kotlinx-coroutines-android
+
+// Compile stub implementations of annotations used by kotlinx-coroutines but not present in the
+// Android tree.
+java_library {
+    name: "kotlinx-coroutines-android-annotation-stubs",
+    host_supported: true,
+    sdk_version: "core_current",
+    srcs: ["android-annotation-stubs/src/**/*.java"],
+}
+
+// Temporary aliases for kotlinx-coroutines-core, kotlinx-coroutines-core-jvm and
+// kotlinx-coroutines-android
 
 java_library {
     name: "kotlinx-coroutines-core",
     host_supported: true,
-    sdk_version: "28",
+    sdk_version: "core_current",
+    min_sdk_version: "28",
     static_libs: ["kotlinx_coroutines"],
     apex_available: [
         "//apex_available:platform",
-        "//apex_available:anyapex"
+        "//apex_available:anyapex",
     ],
+    visibility: ["//visibility:public"],
 }
 
 java_library {
     name: "kotlinx-coroutines-core-jvm",
     host_supported: true,
-    sdk_version: "28",
+    sdk_version: "core_current",
+    min_sdk_version: "28",
     static_libs: ["kotlinx_coroutines"],
     apex_available: [
         "//apex_available:platform",
-        "//apex_available:anyapex"
+        "//apex_available:anyapex",
     ],
+    visibility: ["//visibility:public"],
 }
 
 java_library {
     name: "kotlinx-coroutines-android",
-    sdk_version: "28",
+    sdk_version: "current",
+    min_sdk_version: "28",
     static_libs: [
         "kotlinx_coroutines_android",
         "kotlinx_coroutines",
     ],
     apex_available: [
         "//apex_available:platform",
-        "//apex_available:anyapex"
+        "//apex_available:anyapex",
     ],
+    visibility: ["//visibility:public"],
 }
 
 filegroup {
diff --git a/CHANGES.md b/CHANGES.md
index 611e9c9..adadf23 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,171 @@
 # Change log for kotlinx.coroutines
 
+## Version 1.6.4
+
+* Added `TestScope.backgroundScope` for launching coroutines that perform work in the background and need to be cancelled at the end of the test (#3287).
+* Fixed the POM of `kotlinx-coroutines-debug` having an incorrect reference to `kotlinx-coroutines-bom`, which cause the builds of Maven projects using the debug module to break (#3334).
+* Fixed the `Publisher.await` functions in `kotlinx-coroutines-reactive` not ensuring that the `Subscriber` methods are invoked serially (#3360). Thank you, @EgorKulbachka!
+* Fixed a memory leak in `withTimeout` on K/N with the new memory model (#3351).
+* Added the guarantee that all `Throwable` implementations in the core library are serializable (#3328).
+* Moved the documentation to <https://kotlinlang.org/api/kotlinx.coroutines/> (#3342).
+* Various documentation improvements.
+
+## Version 1.6.3
+
+* Updated atomicfu version to 0.17.3 (#3321), fixing the projects using this library with JS IR failing to build (#3305).
+
+## Version 1.6.2
+
+* Fixed a bug with `ThreadLocalElement` not being correctly updated when the most outer `suspend` function was called directly without `kotlinx.coroutines` (#2930).
+* Fixed multiple data races: one that might have been affecting `runBlocking` event loop, and a benign data race in `Mutex` (#3250, #3251).
+* Obsolete `TestCoroutineContext` is removed, which fixes the `kotlinx-coroutines-test` JPMS package being split between `kotlinx-coroutines-core` and `kotlinx-coroutines-test` (#3218).
+* Updated the ProGuard rules to further shrink the size of the resulting DEX file with coroutines (#3111, #3263). Thanks, @agrieve!
+* Atomicfu is updated to `0.17.2`, which includes a more efficient and robust JS IR transformer (#3255).
+* Kotlin is updated to `1.6.21`, Gradle version is updated to `7.4.2` (#3281). Thanks, @wojtek-kalicinski!
+* Various documentation improvements.
+
+## Version 1.6.1
+
+* Rollback of time-related functions dispatching on `Dispatchers.Main`.
+  This behavior was introduced in 1.6.0 and then found inconvenient and erroneous (#3106, #3113).
+* Reworked the newly-introduced `CopyableThreadContextElement` to solve issues uncovered after the initial release (#3227).
+* Fixed a bug with `ThreadLocalElement` not being properly updated in racy scenarios (#2930).
+* Reverted eager loading of default `CoroutineExceptionHandler` that triggered ANR on some devices (#3180).
+* New API to convert a `CoroutineDispatcher` to a Rx scheduler (#968, #548). Thanks @recheej!
+* Fixed a memory leak with the very last element emitted from `flow` builder being retained in memory (#3197).
+* Fixed a bug with `limitedParallelism` on K/N with new memory model throwing `ClassCastException` (#3223).
+* `CoroutineContext` is added to the exception printed to the default `CoroutineExceptionHandler` to improve debuggability (#3153).
+* Static memory consumption of `Dispatchers.Default` was significantly reduced (#3137).
+* Updated slf4j version in `kotlinx-coroutines-slf4j` from 1.7.25 to 1.7.32.
+
+## Version 1.6.0
+
+Note that this is a full changelog relative to the 1.5.2 version. Changelog relative to 1.6.0-RC3 can be found at the end.
+
+### kotlinx-coroutines-test rework
+
+* `kotlinx-coroutines-test` became a multiplatform library usable from K/JVM, K/JS, and K/N.
+* Its API was completely reworked to address long-standing issues with consistency, structured concurrency and correctness (#1203, #1609, #2379, #1749, #1204, #1390, #1222, #1395, #1881, #1910, #1772, #1626, #1742, #2082, #2102, #2405, #2462
+  ).
+* The old API is deprecated for removal, but the new API is based on the similar concepts ([README](kotlinx-coroutines-test/README.md)), and the migration path is designed to be graceful: [migration guide](kotlinx-coroutines-test/MIGRATION.md).
+
+### Dispatchers
+
+* Introduced `CoroutineDispatcher.limitedParallelism` that allows obtaining a view of the original dispatcher with limited parallelism (#2919).
+* `Dispatchers.IO.limitedParallelism` usages ignore the bound on the parallelism level of `Dispatchers.IO` itself to avoid starvation (#2943).
+* Introduced new `Dispatchers.shutdown` method for containerized environments (#2558).
+* `newSingleThreadContext` and `newFixedThreadPoolContext` are promoted to delicate API (#2919).
+
+### Breaking changes
+
+* When racing with cancellation, the `future` builder no longer reports unhandled exceptions into the global `CoroutineExceptionHandler`. Thanks @vadimsemenov! (#2774, #2791).
+* `Mutex.onLock` is deprecated for removal (#2794).
+* `Dispatchers.Main` is now used as the default source of time for `delay` and `withTimeout` when present(#2972).
+    * To opt-out from this behaviour, `kotlinx.coroutines.main.delay` system property can be set to `false`.
+* Java target of coroutines build is now 8 instead of 6 (#1589).
+* **Source-breaking change**: extension `collect` no longer resolves when used with a non-in-place argument of a functional type. This is a candidate for a fix, uncovered after 1.6.0, see #3107 for the additional details.
+
+### Bug fixes and improvements
+
+* Kotlin is updated to 1.6.0.
+* Kotlin/Native [new memory model](https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/) is now supported in regular builds of coroutines conditionally depending on whether `kotlin.native.binary.memoryModel` is enabled (#2914).
+* Introduced `CopyableThreadContextElement` for mutable context elements shared among multiple coroutines. Thanks @yorickhenning! (#2893).
+* `transformWhile`, `awaitClose`, `ProducerScope`, `merge`, `runningFold`, `runingReduce`, and `scan` are promoted to stable API (#2971).
+* `SharedFlow.subscriptionCount` no longer conflates incoming updates and gives all subscribers a chance to observe a short-lived subscription (#2488, #2863, #2871).
+* `Flow` exception transparency mechanism is improved to be more exception-friendly (#3017, #2860).
+* Cancellation from `flat*` operators that leverage multiple coroutines is no longer propagated upstream (#2964).
+* `SharedFlow.collect` now returns `Nothing` (#2789, #2502).
+* `DisposableHandle` is now `fun interface`, and corresponding inline extension is removed (#2790).
+* `FlowCollector` is now `fun interface`, and corresponding inline extension is removed (#3047).
+* Deprecation level of all previously deprecated signatures is raised (#3024).
+* The version file is shipped with each JAR as a resource (#2941).
+* Unhandled exceptions on K/N are passed to the standard library function `processUnhandledException` (#2981).
+* A direct executor is used for `Task` callbacks in `kotlinx-coroutines-play-services` (#2990).
+* Metadata of coroutines artifacts leverages Gradle platform to have all versions of dependencies aligned (#2865).
+* Default `CoroutineExceptionHandler` is loaded eagerly and does not invoke `ServiceLoader` on its exception-handling path (#2552).
+* Fixed the R8 rules for `ServiceLoader` optimization (#2880).
+* Fixed BlockHound integration false-positives (#2894, #2866, #2937).
+* Fixed the exception handler being invoked several times on Android, thanks to @1zaman (#3056).
+* `SendChannel.trySendBlocking` is now available on Kotlin/Native (#3064).
+* The exception recovery mechanism now uses `ClassValue` when available (#2997).
+* JNA is updated to 5.9.0 to support Apple M1 (#3001).
+* Obsolete method on internal `Delay` interface is deprecated (#2979).
+* Support of deprecated `CommonPool` is removed.
+* `@ExperimentalTime` is no longer needed for methods that use `Duration` (#3041).
+* JDK 1.6 is no longer required for building the project (#3043).
+* New version of Dokka is used, fixing the memory leak when building the coroutines and providing brand new reference visuals (https://kotlinlang.org/api/kotlinx.coroutines/) (#3051, #3054).
+
+### Changelog relative to version 1.6.0-RC3
+
+* Restored MPP binary compatibility on K/JS and K/N (#3104).
+* Fixed Dispatchers.Main not being fully initialized on Android and Swing (#3101).
+
+## Version 1.6.0-RC3
+
+* Fixed the error in 1.6.0-RC2 because of which `Flow.collect` couldn't be called due to the `@InternalCoroutinesApi` annotation (#3082)
+* Fixed some R8 warnings introduced in 1.6.0-RC (#3090)
+* `TestCoroutineScheduler` now provides a `TimeSource` with its virtual time via the `timeSource` property. Thanks @hfhbd! (#3087)
+
+## Version 1.6.0-RC2
+
+* `@ExperimentalTime` is no longer needed for methods that use `Duration` (#3041).
+* `FlowCollector` is now `fun interface`, and corresponding inline extension is removed (#3047).
+* Fixed the exception handler being invoked several times on Android, thanks to @1zaman (#3056).
+* The deprecated `TestCoroutineScope` is no longer sealed, to simplify migration from it (#3072).
+* `runTest` gives more informative errors when it times out waiting for external completion (#3071).
+* `SendChannel.trySendBlocking` is now available on Kotlin/Native (#3064).
+* Fixed the bug due to which `Dispatchers.Main` was not used for `delay` and `withTimeout` (#3046).
+* JDK 1.6 is no longer required for building the project (#3043).
+* New version of Dokka is used, fixing the memory leak when building the coroutines and providing brand new reference visuals (https://kotlinlang.org/api/kotlinx.coroutines/) (#3051, #3054).
+
+## Version 1.6.0-RC
+
+### kotlinx-coroutines-test rework
+
+* `kotlinx-coroutines-test` became a multiplatform library usable from K/JVM, K/JS, and K/N.
+* Its API was completely reworked to address long-standing issues with consistency, structured concurrency and correctness (#1203, #1609, #2379, #1749, #1204, #1390, #1222, #1395, #1881, #1910, #1772, #1626, #1742, #2082, #2102, #2405, #2462
+  ).
+* The old API is deprecated for removal, but the new API is based on the similar concepts ([README](kotlinx-coroutines-test/README.md)), and the migration path is designed to be graceful: [migration guide](kotlinx-coroutines-test/MIGRATION.md)
+
+### Dispatchers
+
+* Introduced `CoroutineDispatcher.limitedParallelism` that allows obtaining a view of the original dispatcher with limited parallelism (#2919).
+* `Dispatchers.IO.limitedParallelism` usages ignore the bound on the parallelism level of `Dispatchers.IO` itself to avoid starvation (#2943).
+* Introduced new `Dispatchers.shutdown` method for containerized environments (#2558).
+* `newSingleThreadContext` and `newFixedThreadPoolContext` are promoted to delicate API (#2919).
+
+### Breaking changes
+
+* When racing with cancellation, the `future` builder no longer reports unhandled exceptions into the global `CoroutineExceptionHandler`. Thanks @vadimsemenov! (#2774, #2791).
+* `Mutex.onLock` is deprecated for removal (#2794).
+* `Dispatchers.Main` is now used as the default source of time for `delay` and `withTimeout` when present(#2972).
+    * To opt-out from this behaviour, `kotlinx.coroutines.main.delay` system property can be set to `false`.
+* Java target of coroutines build is now 8 instead of 6 (#1589).
+
+### Bug fixes and improvements
+
+* Kotlin is updated to 1.6.0.
+* Kotlin/Native [new memory model](https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/) is now supported in regular builds of coroutines conditionally depending on whether `kotlin.native.binary.memoryModel` is enabled (#2914).
+* Introduced `CopyableThreadContextElement` for mutable context elements shared among multiple coroutines. Thanks @yorickhenning! (#2893).
+* `transformWhile`, `awaitClose`, `ProducerScope`, `merge`, `runningFold`, `runingReduce`, and `scan` are promoted to stable API (#2971).
+* `SharedFlow.subscriptionCount` no longer conflates incoming updates and gives all subscribers a chance to observe a short-lived subscription (#2488, #2863, #2871).
+* `Flow` exception transparency mechanism is improved to be more exception-friendly (#3017, #2860).
+* Cancellation from `flat*` operators that leverage multiple coroutines is no longer propagated upstream (#2964).
+* `SharedFlow.collect` now returns `Nothing` (#2789, #2502).
+* `DisposableHandle` is now `fun interface`, and corresponding inline extension is removed (#2790).
+* Deprecation level of all previously deprecated signatures is raised (#3024).
+* The version file is shipped with each JAR as a resource (#2941).
+* Unhandled exceptions on K/N are passed to the standard library function `processUnhandledException` (#2981).
+* A direct executor is used for `Task` callbacks in `kotlinx-coroutines-play-services` (#2990).
+* Metadata of coroutines artifacts leverages Gradle platform to have all versions of dependencies aligned (#2865).
+* Default `CoroutineExceptionHandler` is loaded eagerly and does not invoke `ServiceLoader` on its exception-handling path (#2552).
+* Fixed the R8 rules for `ServiceLoader` optimization (#2880).
+* Fixed BlockHound integration false-positives (#2894, #2866, #2937).
+* The exception recovery mechanism now uses `ClassValue` when available (#2997).
+* JNA is updated to 5.9.0 to support Apple M1 (#3001).
+* Obsolete method on internal `Delay` interface is deprecated (#2979).
+* Support of deprecated `CommonPool` is removed.
+
 ## Version 1.5.2
 
 * Kotlin is updated to 1.5.30.
@@ -363,7 +529,7 @@
 
 ### 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.
+This version is the first stable release with [`Flow`](https://kotlinlang.org/api/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/topics/compatibility.md#experimental-api) as regular experimental API.
@@ -692,9 +858,9 @@
   * 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`.
+  * [CoroutineScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html) 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.
+  * [GlobalScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html) 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.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7d6e32d..77b727b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,15 +80,12 @@
 ### Environment requirements
 
 * JDK >= 11 referred to by the `JAVA_HOME` environment variable.
-* JDK 1.6 referred to by the `JDK_16` environment variable. 
-  It is OK to have `JDK_16` pointing to a non 1.6 JDK (e.g. `JAVA_HOME`) for external contributions.
 * JDK 1.8 referred to by the `JDK_18` environment variable. Only used by nightly stress-tests. 
   It is OK to have `JDK_18` to a non 1.8 JDK (e.g. `JAVA_HOME`) for external contributions.
-  
+
 For external contributions you can for example add this to your shell startup scripts (e.g. `~/.zshrc`):
 ```shell
 # This assumes JAVA_HOME is set already to a JDK >= 11 version
-export JDK_16="$JAVA_HOME"
 export JDK_18="$JAVA_HOME"
 ```
 
diff --git a/METADATA b/METADATA
index 798558c..9826606 100644
--- a/METADATA
+++ b/METADATA
@@ -5,11 +5,11 @@
     type: GIT
     value: "https://github.com/Kotlin/kotlinx.coroutines"
   }
-  version: "1.5.2"
+  version: "1.6.4"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2021
-    month: 11
-    day: 01
+    year: 2022
+    month: 7
+    day: 20
   }
 }
diff --git a/README.md b/README.md
index 6a13f07..d9019dc 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@
 # kotlinx.coroutines 
 
-[![official JetBrains project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
+[![Kotlin Stable](https://kotl.in/badges/stable.svg)](https://kotlinlang.org/docs/components-stability.html)
+[![JetBrains official 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://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.2)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.2/pom)
-[![Kotlin](https://img.shields.io/badge/kotlin-1.5.30-blue.svg?logo=kotlin)](http://kotlinlang.org)
+[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4/pom)
+[![Kotlin](https://img.shields.io/badge/kotlin-1.6.21-blue.svg?logo=kotlin)](http://kotlinlang.org)
 [![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/)
 
 Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
-This is a companion version for the Kotlin `1.5.30` release.
+This is a companion version for the Kotlin `1.6.21` release.
 
 ```kotlin
 suspend fun main() = coroutineScope {
@@ -61,9 +62,9 @@
 ## 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))
+  * [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))
+  * [History of Structured Concurrency in Coroutines](https://www.youtube.com/watch?v=Mj5P47F6nJg) (Roman Elizarov at Hydra 2019, [slides](https://speakerdeck.com/elizarov/structured-concurrency))
 * Guides and manuals: 
   * [Guide to kotlinx.coroutines by example](https://kotlinlang.org/docs/coroutines-guide.html) (**read it first**)
   * [Guide to UI programming with coroutines](ui/coroutines-guide-ui.md)
@@ -71,7 +72,7 @@
 * [Compatibility policy and experimental annotations](docs/topics/compatibility.md)
 * [Change log for kotlinx.coroutines](CHANGES.md)
 * [Coroutines design document (KEEP)](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md)
-* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines)
+* [Full kotlinx.coroutines API reference](https://kotlinlang.org/api/kotlinx.coroutines/)
  
 ## Using in your projects
 
@@ -83,7 +84,7 @@
 <dependency>
     <groupId>org.jetbrains.kotlinx</groupId>
     <artifactId>kotlinx-coroutines-core</artifactId>
-    <version>1.5.2</version>
+    <version>1.6.4</version>
 </dependency>
 ```
 
@@ -91,7 +92,7 @@
 
 ```xml
 <properties>
-    <kotlin.version>1.5.30</kotlin.version>
+    <kotlin.version>1.6.21</kotlin.version>
 </properties>
 ```
 
@@ -99,55 +100,39 @@
 
 Add dependencies (you can also add other modules that you need):
 
-```groovy
+```kotlin
 dependencies {
-    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
+    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
 }
 ```
 
 And make sure that you use the latest Kotlin version:
 
-```groovy
-buildscript {
-    ext.kotlin_version = '1.5.30'
+```kotlin
+plugins {
+    // For build.gradle.kts (Kotlin DSL)
+    kotlin("jvm") version "1.6.21"
+    
+    // For build.gradle (Groovy DSL)
+    id "org.jetbrains.kotlin.jvm" version "1.6.21"
 }
 ```
 
 Make sure that you have `mavenCentral()` in the list of repositories:
 
-```
-repository {
+```kotlin
+repositories {
     mavenCentral()
 }
 ```
 
-### Gradle Kotlin DSL
-
-Add dependencies (you can also add other modules that you need):
-
-```groovy
-dependencies {
-    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
-}
-```
-
-And make sure that you use the latest Kotlin version:
-
-```groovy
-plugins {
-    kotlin("jvm") version "1.5.20"
-}
-```
-
-Make sure that you have `mavenCentral()` in the list of repositories.
-
 ### Android
 
 Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
 module as a dependency when using `kotlinx.coroutines` on Android:
 
-```groovy
-implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
+```kotlin
+implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
 ```
 
 This gives you access to the Android [Dispatchers.Main]
@@ -165,7 +150,8 @@
 The `kotlinx-coroutines-core` artifact contains a resource file that is not required for the coroutines to operate
 normally and is only used by the debugger. To exclude it at no loss of functionality, add the following snippet to the
 `android` block in your Gradle file for the application subproject:
-```groovy
+
+```kotlin
 packagingOptions {
     resources.excludes += "DebugProbesKt.bin"
 }
@@ -177,10 +163,11 @@
 [Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html) and [Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html).
 
 In common code that should get compiled for different platforms, you can add a dependency to `kotlinx-coroutines-core` right to the `commonMain` source set:
-```groovy
+
+```kotlin
 commonMain {
     dependencies {
-        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
+        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
     }
 }
 ```
@@ -192,7 +179,7 @@
 #### JS
 
 Kotlin/JS version of `kotlinx.coroutines` is published as 
-[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.5.2/jar)
+[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.6.4/jar)
 (follow the link to get the dependency declaration snippet) and as [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) NPM package. 
 
 #### Native
@@ -201,14 +188,6 @@
 [`kotlinx-coroutines-core-$platform`](https://mvnrepository.com/search?q=kotlinx-coroutines-core-) where `$platform` is 
 the target Kotlin/Native platform. [List of currently supported targets](https://github.com/Kotlin/kotlinx.coroutines/blob/master/gradle/compile-native-multiplatform.gradle#L16).
 
-
-Only single-threaded code (JS-style) on Kotlin/Native is supported in stable versions.
-Additionally, a special `-native-mt` version is released on a regular basis, for the state of multi-threaded coroutines support
-please follow the [corresponding issue](https://github.com/Kotlin/kotlinx.coroutines/issues/462) for the additional details.
-
-Since Kotlin/Native does not generally provide binary compatibility between versions, 
-you should use the same version of the Kotlin/Native compiler as was used to build `kotlinx.coroutines`. 
-
 ## Building and Contributing
 
 See [Contributing Guidelines](CONTRIBUTING.md).
@@ -216,102 +195,102 @@
 <!--- 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
-[asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
-[Promise.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await.html
-[promise]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/promise.html
-[Window.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
+[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[Dispatchers]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/index.html
+[Dispatchers.Main]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
+[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[yield]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
+[_coroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
+[_supervisorScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
+[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[withTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
+[MainScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
+[SupervisorJob()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html
+[CoroutineExceptionHandler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
+[Dispatchers.IO]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html
+[asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
+[Promise.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await.html
+[promise]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/promise.html
+[Window.asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
 
 <!--- INDEX kotlinx.coroutines.flow -->
 
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
-[_flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
-[filter]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/filter.html
-[map]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html
+[Flow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+[_flow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
+[filter]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/filter.html
+[map]: https://kotlinlang.org/api/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
+[Channel]: https://kotlinlang.org/api/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
+[select]: https://kotlinlang.org/api/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
+[Mutex]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
+[Semaphore]: https://kotlinlang.org/api/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/set-main.html
-[TestCoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/index.html
+[Dispatchers.setMain]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html
+[TestCoroutineScope]: https://kotlinlang.org/api/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
+[DebugProbes]: https://kotlinlang.org/api/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
+[CoroutinesTimeout]: https://kotlinlang.org/api/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
+[MDCContext]: https://kotlinlang.org/api/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/await.html
+[CompletionStage.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html
 
 <!--- MODULE kotlinx-coroutines-guava -->
 <!--- INDEX kotlinx.coroutines.guava -->
 
-[ListenableFuture.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/await.html
+[ListenableFuture.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/await.html
 
 <!--- MODULE kotlinx-coroutines-play-services -->
 <!--- INDEX kotlinx.coroutines.tasks -->
 
-[Task.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/await.html
+[Task.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/await.html
 
 <!--- MODULE kotlinx-coroutines-reactive -->
 <!--- INDEX kotlinx.coroutines.reactive -->
 
-[Publisher.collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/collect.html
-[Publisher.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-single.html
-[kotlinx.coroutines.reactive.publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
+[Publisher.collect]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/collect.html
+[Publisher.awaitSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-single.html
+[kotlinx.coroutines.reactive.publish]: https://kotlinlang.org/api/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
+[rxFlowable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-flowable.html
+[rxSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-single.html
 
 <!--- MODULE kotlinx-coroutines-rx2 -->
 <!--- INDEX kotlinx.coroutines.rx2 -->
 <!--- MODULE kotlinx-coroutines-reactor -->
 <!--- INDEX kotlinx.coroutines.reactor -->
 
-[flux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/flux.html
-[mono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/mono.html
+[flux]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/flux.html
+[mono]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/mono.html
 
 <!--- END -->
diff --git a/RELEASE.md b/RELEASE.md
index edc7726..ef7cc23 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,15 +1,15 @@
 # kotlinx.coroutines release checklist
 
-To release new `<version>` of `kotlinx-coroutines`:
+To release a new `<version>` of `kotlinx-coroutines`:
 
-1. Checkout `develop` branch: <br> 
+1. Checkout the `develop` branch: <br>
    `git checkout develop`
 
-2. Retrieve the most recent `develop`: <br> 
+2. Retrieve the most recent `develop`: <br>
    `git pull`
-   
+
 3. Make sure the `master` branch is fully merged into `develop`:
-   `git merge origin/master`   
+   `git merge origin/master`
 
 4. Search & replace `<old-version>` with `<version>` across the project files. Should replace in:
    * Docs
@@ -17,62 +17,65 @@
      * [`kotlinx-coroutines-debug/README.md`](kotlinx-coroutines-debug/README.md)
      * [`kotlinx-coroutines-test/README.md`](kotlinx-coroutines-test/README.md)
      * [`coroutines-guide-ui.md`](ui/coroutines-guide-ui.md)
-   * Properties   
-     * [`gradle.properties`](gradle.properties)  
+   * Properties
+     * [`gradle.properties`](gradle.properties)
    * Make sure to **exclude** `CHANGES.md` from replacements.
-   
-   As an alternative approach you can use `./bump-version.sh old_version new_version`
-  
+
+   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.
+   * Use the old releases for style guidance.
    * Write each change on a single line (don't wrap with CR).
-   * Study commit message from previous release.
+   * Look through the commit messages since the previous release.
 
 6. Create the branch for this release:
    `git checkout -b version-<version>`
 
-7. Commit updated files to a new version branch:<br>
+7. Commit the updated files to the new version branch:<br>
    `git commit -a -m "Version <version>"`
-   
-8. Push the new version into the branch:<br>
+
+8. Push the new version to GitHub:<br>
    `git push -u origin version-<version>`
-   
-9. Create Pull-Request on GitHub from `version-<version>` branch into `master`:
+
+9. Create a Pull-Request on GitHub from the `version-<version>` branch into `master`:
    * Review it.
-   * Make sure it build on CI.
+   * Make sure it builds 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):
+0. 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.    
+   * Run "Deploy (Configure, RUN THIS ONE)" configuration with the corresponding new version:
+     - Use the `version-<version>` branch
+     - Set the `DeployVersion` build parameter to `<version>`
+   * Wait until all four "Deploy" configurations finish.
 
-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
-   (make sure you have [Docker](https://www.docker.com/) installed first): <br>
-   `site/deploy.sh <version> push`
-   
-4. In [Nexus](https://oss.sonatype.org/#stagingRepositories) admin interface:
+1. In [Nexus](https://oss.sonatype.org/#stagingRepositories) admin interface:
    * Close the repository and wait for it to verify.
    * Release the repository.
-   
-5. Announce new release in [Slack](https://kotlinlang.slack.com)   
 
-6. Switch into `develop` branch:<br>
+2. Merge the new version branch into `master`:<br>
+   `git checkout master`<br>
+   `git merge version-<version>`<br>
+   `git push`
+
+3. In [GitHub](https://github.com/kotlin/kotlinx.coroutines) interface:
+   * Create a release named `<version>`, creating the `<version>` tag.
+   * Cut & paste lines from [`CHANGES.md`](CHANGES.md) into description.
+
+4. Announce the new release in [Slack](https://kotlinlang.slack.com)
+
+5. Switch onto the `develop` branch:<br>
    `git checkout develop`
- 
-7. Fetch the latest `master`:<br>
-   `git fetch` 
-   
-8. Merge release from `master`:<br>
+
+6. Fetch the latest `master`:<br>
+   `git fetch`
+
+7. Merge the release from `master`:<br>
    `git merge origin/master`
-   
-9. Push updates to `develop`:<br>
-   `git push`      
+
+8. Push the updates to GitHub:<br>
+   `git push`
+
+9. Build and publish the documentation for the web-site: <br>
+   * Set new value for [`kotlinx.coroutines.release.tag`](https://buildserver.labs.intellij.net/admin/editProject.html?projectId=Kotlin_KotlinSites_Builds_KotlinlangOrg_LibrariesAPIs&tab=projectParams)
+   * And run deploy [configuration](https://buildserver.labs.intellij.net/buildConfiguration/Kotlin_KotlinSites_Builds_KotlinlangOrg_KotlinCoroutinesApi?branch=%3Cdefault%3E&buildTypeTab=overview&mode=builds)
diff --git a/android-annotation-stubs/gen_annotations.sh b/android-annotation-stubs/gen_annotations.sh
new file mode 100755
index 0000000..97d4597
--- /dev/null
+++ b/android-annotation-stubs/gen_annotations.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+ANNOTATIONS=(
+    org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
+)
+
+for a in ${ANNOTATIONS[@]}; do
+    package=${a%.*}
+    class=${a##*.}
+    dir=$(dirname $0)/src/${package//.//}
+    file=${class}.java
+
+    mkdir -p ${dir}
+    sed -e"s/__PACKAGE__/${package}/" -e"s/__CLASS__/${class}/" tmpl.java > ${dir}/${file}
+done
diff --git a/android-annotation-stubs/src/org/codehaus/mojo/animal_sniffer/IgnoreJRERequirement.java b/android-annotation-stubs/src/org/codehaus/mojo/animal_sniffer/IgnoreJRERequirement.java
new file mode 100644
index 0000000..898bcba
--- /dev/null
+++ b/android-annotation-stubs/src/org/codehaus/mojo/animal_sniffer/IgnoreJRERequirement.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package org.codehaus.mojo.animal_sniffer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/* This is an annotation stub to avoid dependencies on annotations that aren't
+ * in the Android platform source tree. */
+
+@Target({
+    ElementType.ANNOTATION_TYPE,
+    ElementType.CONSTRUCTOR,
+    ElementType.FIELD,
+    ElementType.LOCAL_VARIABLE,
+    ElementType.METHOD,
+    ElementType.PACKAGE,
+    ElementType.PARAMETER,
+    ElementType.TYPE,
+    ElementType.TYPE_PARAMETER,
+    ElementType.TYPE_USE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface IgnoreJRERequirement {}
diff --git a/android-annotation-stubs/tmpl.java b/android-annotation-stubs/tmpl.java
new file mode 100644
index 0000000..91868f2
--- /dev/null
+++ b/android-annotation-stubs/tmpl.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package __PACKAGE__;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/* This is an annotation stub to avoid dependencies on annotations that aren't
+ * in the Android platform source tree. */
+
+@Target({
+    ElementType.ANNOTATION_TYPE,
+    ElementType.CONSTRUCTOR,
+    ElementType.FIELD,
+    ElementType.LOCAL_VARIABLE,
+    ElementType.METHOD,
+    ElementType.PACKAGE,
+    ElementType.PARAMETER,
+    ElementType.TYPE,
+    ElementType.TYPE_PARAMETER,
+    ElementType.TYPE_USE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface __CLASS__ {}
diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts
index ce0bff1..f64c4aa 100644
--- a/benchmarks/build.gradle.kts
+++ b/benchmarks/build.gradle.kts
@@ -8,7 +8,6 @@
 import org.jetbrains.kotlin.gradle.tasks.*
 
 plugins {
-    id("net.ltgt.apt")
     id("com.github.johnrengelman.shadow")
     id("me.champeau.gradle.jmh") apply false
 }
@@ -31,8 +30,6 @@
     }
 }
 
-
-
 // It is better to use the following to run benchmarks, otherwise you may get unexpected errors:
 // ./gradlew --no-daemon cleanJmhJar jmh -Pjmh="MyBenchmark"
 extensions.configure<JMHPluginExtension>("jmh") {
@@ -46,21 +43,34 @@
 //    includeTests = false
 }
 
-tasks.named<Jar>("jmhJar") {
+val jmhJarTask = tasks.named<Jar>("jmhJar") {
     archiveBaseName by "benchmarks"
     archiveClassifier by null
     archiveVersion by null
     destinationDirectory.file("$rootDir")
 }
 
-dependencies {
-    compile("org.openjdk.jmh:jmh-core:1.26")
-    compile("io.projectreactor:reactor-core:${version("reactor")}")
-    compile("io.reactivex.rxjava2:rxjava:2.1.9")
-    compile("com.github.akarnokd:rxjava2-extensions:0.20.8")
+tasks {
+    // For some reason the DuplicatesStrategy from jmh is not enough
+    // and errors with duplicates appear unless I force it to WARN only:
+    withType<Copy> {
+        duplicatesStrategy = DuplicatesStrategy.WARN
+    }
 
-    compile("com.typesafe.akka:akka-actor_2.12:2.5.0")
-    compile(project(":kotlinx-coroutines-core"))
+    build {
+        dependsOn(jmhJarTask)
+    }
+}
+
+dependencies {
+    implementation("org.openjdk.jmh:jmh-core:1.26")
+    implementation("io.projectreactor:reactor-core:${version("reactor")}")
+    implementation("io.reactivex.rxjava2:rxjava:2.1.9")
+    implementation("com.github.akarnokd:rxjava2-extensions:0.20.8")
+
+    implementation("com.typesafe.akka:akka-actor_2.12:2.5.0")
+    implementation(project(":kotlinx-coroutines-core"))
+    implementation(project(":kotlinx-coroutines-reactive"))
 
     // add jmh dependency on main
     "jmhImplementation"(sourceSets.main.get().runtimeClasspath)
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
index 9948a37..ce64c6a 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
@@ -25,12 +25,11 @@
     private var closeable: Closeable? = null
 
     @Setup
-    @UseExperimental(InternalCoroutinesApi::class)
     open fun setup() {
         coroutineContext = when {
             dispatcher == "fjp" -> ForkJoinPool.commonPool().asCoroutineDispatcher()
             dispatcher == "scheduler" -> {
-                ExperimentalCoroutineDispatcher(CORES_COUNT).also { closeable = it }
+                Dispatchers.Default
             }
             dispatcher.startsWith("ftp") -> {
                 newFixedThreadPoolContext(dispatcher.substring(4).toInt(), dispatcher).also { closeable = it }
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
index 40ddc8e..9e1bfc4 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
@@ -6,13 +6,10 @@
 
 import benchmarks.common.*
 import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher
-import kotlinx.coroutines.sync.Semaphore
-import kotlinx.coroutines.sync.withPermit
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.sync.*
 import org.openjdk.jmh.annotations.*
-import java.util.concurrent.ForkJoinPool
-import java.util.concurrent.TimeUnit
+import java.util.concurrent.*
 
 @Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MICROSECONDS)
 @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MICROSECONDS)
@@ -84,7 +81,7 @@
 
 enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) {
     FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }),
-    EXPERIMENTAL({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) })
+    EXPERIMENTAL({ parallelism -> Dispatchers.Default }) // TODO doesn't take parallelism into account
 }
 
 private const val WORK_INSIDE = 80
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
index e7bd1f5..acfb3f3 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
@@ -30,7 +30,7 @@
         val bonusForDoubleLetter: (String) -> Int = { word: String ->
             toBeMaxed(word)
                 .map { letterScores[it - 'a'.toInt()] }
-                .max()!!
+                .maxOrNull()!!
         }
 
         val score3: (String) -> Int = { word: String ->
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
index a6f0a47..d874f3b 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
@@ -27,10 +27,8 @@
 @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 experimental = Dispatchers.Default
+    private val blocking = Dispatchers.IO.limitedParallelism(8)
     private val threadPool = newFixedThreadPoolContext(8, "PongCtx")
 
     @TearDown
diff --git a/build.gradle b/build.gradle
index e4b12ff..4d6af16 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,21 +2,16 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
+
+import org.jetbrains.kotlin.config.KotlinCompilerVersion
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
 import org.jetbrains.kotlin.konan.target.HostManager
-import org.gradle.util.VersionNumber
 import org.jetbrains.dokka.gradle.DokkaTaskPartial
-import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
+
+import static Projects.*
 
 apply plugin: 'jdk-convention'
-apply from: rootProject.file("gradle/opt-in.gradle")
-
-def coreModule = "kotlinx-coroutines-core"
-// Not applicable for Kotlin plugin
-def sourceless = ['kotlinx.coroutines', 'kotlinx-coroutines-bom', 'integration-testing']
-def internal = ['kotlinx.coroutines', 'benchmarks', 'integration-testing']
-// Not published
-def unpublished = internal + ['example-frontend-js', 'android-unit-tests']
 
 buildscript {
     /*
@@ -47,12 +42,6 @@
         }
     }
 
-    if (using_snapshot_version) {
-        repositories {
-            mavenLocal()
-        }
-    }
-
     repositories {
         mavenCentral()
         maven { url "https://plugins.gradle.org/m2/" }
@@ -65,11 +54,13 @@
         classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
         classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
         classpath "org.jetbrains.kotlinx:kotlinx-knit:$knit_version"
-        classpath "com.moowork.gradle:gradle-node-plugin:$gradle_node_version"
+        classpath "com.github.node-gradle:gradle-node-plugin:$gradle_node_version"
         classpath "org.jetbrains.kotlinx:binary-compatibility-validator:$binary_compatibility_validator_version"
+        classpath "ru.vyarus:gradle-animalsniffer-plugin:1.5.4" // Android API check
+        classpath "org.jetbrains.kotlinx:kover:$kover_version"
 
         // JMH plugins
-        classpath "com.github.jengelman.gradle.plugins:shadow:5.1.0"
+        classpath "gradle.plugin.com.github.johnrengelman:shadow:7.1.2"
     }
 
     CacheRedirector.configureBuildScript(buildscript, rootProject)
@@ -102,13 +93,6 @@
         kotlin_version = rootProject.properties['kotlin_snapshot_version']
     }
 
-    if (using_snapshot_version) {
-        repositories {
-            mavenLocal()
-            maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
-        }
-    }
-
     ext.unpublished = unpublished
 
     // This project property is set during nightly stress test
@@ -121,11 +105,14 @@
 }
 
 apply plugin: "binary-compatibility-validator"
+apply plugin: "base"
+apply plugin: "kover-conventions"
+
 apiValidation {
     ignoredProjects += unpublished + ["kotlinx-coroutines-bom"]
     if (build_snapshot_train) {
         ignoredProjects.remove("example-frontend-js")
-        ignoredProjects.add("kotlinx-coroutines-core")
+        ignoredProjects.add(coreModule)
     }
     ignoredPackages += "kotlinx.coroutines.internal"
 }
@@ -139,30 +126,54 @@
          */
         google()
         mavenCentral()
+        maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
     }
 }
 
+// needs to be before evaluationDependsOn due to weird Gradle ordering
+apply plugin: "animalsniffer-conventions"
+
 // Add dependency to core source sets. Core is configured in kx-core/build.gradle
 configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != coreModule }) {
     evaluationDependsOn(":$coreModule")
-    def platform = PlatformKt.platformOf(it)
-    apply plugin: "kotlin-${platform}-conventions"
-    dependencies {
-        // See comment below for rationale, it will be replaced with "project" dependency
-        api project(":$coreModule")
-        // the only way IDEA can resolve test classes
-        testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+    if (isMultiplatform(it)) {
+        apply plugin: "kotlin-multiplatform"
+        apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle")
+        apply from: rootProject.file("gradle/compile-common.gradle")
+
+        if (rootProject.ext["native_targets_enabled"] as Boolean) {
+            apply from: rootProject.file("gradle/compile-native-multiplatform.gradle")
+        }
+
+        apply from: rootProject.file("gradle/compile-js-multiplatform.gradle")
+        apply from: rootProject.file("gradle/publish-npm-js.gradle")
+        kotlin.sourceSets.commonMain.dependencies {
+            api project(":$coreModule")
+        }
+        kotlin.sourceSets.jvmTest.dependencies {
+            implementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+        }
+    } else {
+        def platform = PlatformKt.platformOf(it)
+        apply plugin: "kotlin-${platform}-conventions"
+        dependencies {
+            api project(":$coreModule")
+            // the only way IDEA can resolve test classes
+            testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+        }
     }
 }
 
+apply plugin: "bom-conventions"
+
 // Configure subprojects with Kotlin sources
 configure(subprojects.findAll { !sourceless.contains(it.name) }) {
     // Use atomicfu plugin, it also adds all the necessary dependencies
     apply plugin: 'kotlinx-atomicfu'
 
     // Configure options for all Kotlin compilation tasks
-    tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
-        kotlinOptions.freeCompilerArgs += optInAnnotations.collect { "-Xopt-in=" + it }
+    tasks.withType(AbstractKotlinCompile).all {
+        kotlinOptions.freeCompilerArgs += OptInPreset.optInAnnotations.collect { "-Xopt-in=" + it }
         kotlinOptions.freeCompilerArgs += "-progressive"
         // Disable KT-36770 for RxJava2 integration
         kotlinOptions.freeCompilerArgs += "-XXLanguage:-ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated"
@@ -189,7 +200,7 @@
     }
 
     println "Manifest of kotlin-compiler-embeddable.jar for coroutines"
-    configure(subprojects.findAll { it.name == "kotlinx-coroutines-core" }) {
+    configure(subprojects.findAll { it.name == coreModule }) {
         configurations.matching { it.name == "kotlinCompilerClasspath" }.all {
             resolvedConfiguration.getFiles().findAll { it.name.contains("kotlin-compiler-embeddable") }.each {
                 def manifest = zipTree(it).matching {
@@ -206,9 +217,8 @@
 
 // Redefine source sets because we are not using 'kotlin/main/fqn' folder convention
 configure(subprojects.findAll {
-    !sourceless.contains(it.name) &&
+    !sourceless.contains(it.name) && !isMultiplatform(it) &&
             it.name != "benchmarks" &&
-            it.name != coreModule &&
             it.name != "example-frontend-js"
 }) {
     // Pure JS and pure MPP doesn't have this notion and are configured separately
@@ -221,11 +231,11 @@
     }
 }
 
-def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/$coreModule/"
+def core_docs_url = "https://kotlinlang.org/api/kotlinx.coroutines/$coreModule/"
 def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/htmlPartial/package-list"
 apply plugin: "org.jetbrains.dokka"
 
-configure(subprojects.findAll { !unpublished.contains(it.name) }) {
+configure(subprojects.findAll { !unpublished.contains(it.name) && it.name != coreModule }) {
     if (it.name != 'kotlinx-coroutines-bom') {
         apply from: rootProject.file('gradle/dokka.gradle.kts')
     }
@@ -245,10 +255,44 @@
             }
         }
     }
+
+    def thisProject = it
+    if (thisProject.name in sourceless) {
+        return
+    }
+
+    def versionFileTask = thisProject.tasks.register("versionFileTask") {
+        def name = thisProject.name.replace("-", "_")
+        def versionFile = thisProject.layout.buildDirectory.file("${name}.version")
+        it.outputs.file(versionFile)
+
+        it.doLast {
+            versionFile.get().asFile.text = version.toString()
+        }
+    }
+
+    List<String> jarTasks
+    if (isMultiplatform(it)) {
+        jarTasks = ["jvmJar", "metadataJar"]
+    } else if (it.name == "kotlinx-coroutines-debug") {
+        // We shadow debug module instead of just packaging it
+        jarTasks = ["shadowJar"]
+    } else {
+        jarTasks = ["jar"]
+    }
+
+    for (name in jarTasks) {
+        thisProject.tasks.named(name, Jar) {
+            it.dependsOn versionFileTask
+            it.from(versionFileTask) {
+                into("META-INF")
+            }
+        }
+    }
 }
 
 // Report Kotlin compiler version when building project
-println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION")
+println("Using Kotlin compiler version: $KotlinCompilerVersion.VERSION")
 
 // --------------- Cache redirector ---------------
 
@@ -262,8 +306,6 @@
 
 task deploy(dependsOn: publishTasks)
 
-apply plugin: 'base'
-
 clean.dependsOn gradle.includedBuilds.collect { it.task(':clean') }
 
 // --------------- Knit configuration ---------------
@@ -271,7 +313,7 @@
 apply plugin: 'kotlinx-knit'
 
 knit {
-    siteRoot = "https://kotlin.github.io/kotlinx.coroutines"
+    siteRoot = "https://kotlinlang.org/api/kotlinx.coroutines"
     moduleRoots = [".", "integration", "reactive", "ui"]
     moduleDocs = "build/dokka/htmlPartial"
     dokkaMultiModuleRoot = "build/dokka/htmlMultiModule/"
@@ -302,12 +344,12 @@
             .matching {
                 // Excluding substituted project itself because of circular dependencies, but still do it
                 // for "*Test*" configurations
-                subProject.name != "kotlinx-coroutines-core" || it.name.contains("Test")
+                subProject.name != coreModule || it.name.contains("Test")
             }
             .configureEach { conf ->
                 conf.resolutionStrategy.dependencySubstitution {
-                    substitute(module("org.jetbrains.kotlinx:kotlinx-coroutines-core"))
-                            .using(project(":kotlinx-coroutines-core"))
+                    substitute(module("org.jetbrains.kotlinx:$coreModule"))
+                            .using(project(":$coreModule"))
                             .because("Because Kotlin compiler embeddable leaks coroutines into the runtime classpath, " +
                                     "triggering all sort of incompatible class changes errors")
                 }
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index c54e226..eaa03f2 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -19,7 +19,6 @@
         maven("https://plugins.gradle.org/m2")
     }
     maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
-
     if (buildSnapshotTrain) {
         mavenLocal()
     }
@@ -44,6 +43,25 @@
 
 dependencies {
     implementation(kotlin("gradle-plugin", version("kotlin")))
-    implementation("org.jetbrains.dokka:dokka-gradle-plugin:${version("dokka")}")
-    implementation("org.jetbrains.dokka:dokka-core:${version("dokka")}")
+    /*
+     * Dokka is compiled with language level = 1.4, but depends on Kotlin 1.6.0, while
+     * our version of Gradle bundles Kotlin 1.4.x and can read metadata only up to 1.5.x,
+     * thus we're excluding stdlib compiled with 1.6.0 from dependencies.
+     */
+    implementation("org.jetbrains.dokka:dokka-gradle-plugin:${version("dokka")}") {
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+    }
+    implementation("org.jetbrains.dokka:dokka-core:${version("dokka")}") {
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+    }
+    implementation("ru.vyarus:gradle-animalsniffer-plugin:1.5.3") // Android API check
+    implementation("org.jetbrains.kotlinx:kover:${version("kover")}") {
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
+        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+    }
 }
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
index e30c3ee..c2e859f 100644
--- a/buildSrc/settings.gradle.kts
+++ b/buildSrc/settings.gradle.kts
@@ -4,7 +4,6 @@
 pluginManagement {
     val build_snapshot_train: String? by settings
     repositories {
-        maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev/")
         val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolean() == true
         if (cacheRedirectorEnabled) {
             println("Redirecting repositories for buildSrc buildscript")
diff --git a/buildSrc/src/main/kotlin/OptInPreset.kt b/buildSrc/src/main/kotlin/OptInPreset.kt
new file mode 100644
index 0000000..fdcdb8e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/OptInPreset.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:JvmName("OptInPreset")
+
+val optInAnnotations = listOf(
+    "kotlin.RequiresOptIn",
+    "kotlin.experimental.ExperimentalTypeInference",
+    "kotlin.ExperimentalMultiplatform",
+    "kotlinx.coroutines.DelicateCoroutinesApi",
+    "kotlinx.coroutines.ExperimentalCoroutinesApi",
+    "kotlinx.coroutines.ObsoleteCoroutinesApi",
+    "kotlinx.coroutines.InternalCoroutinesApi",
+    "kotlinx.coroutines.FlowPreview")
diff --git a/buildSrc/src/main/kotlin/Projects.kt b/buildSrc/src/main/kotlin/Projects.kt
index dd284b6..af70989 100644
--- a/buildSrc/src/main/kotlin/Projects.kt
+++ b/buildSrc/src/main/kotlin/Projects.kt
@@ -1,8 +1,31 @@
 /*
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
-
-import org.gradle.api.Project
+@file:JvmName("Projects")
+import org.gradle.api.*
 
 fun Project.version(target: String): String =
     property("${target}_version") as String
+
+val coreModule = "kotlinx-coroutines-core"
+val testModule = "kotlinx-coroutines-test"
+
+val multiplatform = setOf(coreModule, testModule)
+// Not applicable for Kotlin plugin
+val sourceless = setOf("kotlinx.coroutines", "kotlinx-coroutines-bom")
+val internal = setOf("kotlinx.coroutines", "benchmarks")
+// Not published
+val unpublished = internal + setOf("example-frontend-js", "android-unit-tests")
+
+val Project.isMultiplatform: Boolean get() = name in multiplatform
+
+// Projects that we do not check for Android API level 14 check due to various limitations
+val androidNonCompatibleProjects = setOf(
+    "kotlinx-coroutines-debug",
+    "kotlinx-coroutines-swing",
+    "kotlinx-coroutines-javafx",
+    "kotlinx-coroutines-jdk8",
+    "kotlinx-coroutines-jdk9",
+    "kotlinx-coroutines-reactor",
+    "kotlinx-coroutines-test"
+)
diff --git a/buildSrc/src/main/kotlin/Publishing.kt b/buildSrc/src/main/kotlin/Publishing.kt
index 8c6dd5d..cb612c5 100644
--- a/buildSrc/src/main/kotlin/Publishing.kt
+++ b/buildSrc/src/main/kotlin/Publishing.kt
@@ -7,6 +7,7 @@
 import org.gradle.api.Project
 import org.gradle.api.artifacts.dsl.*
 import org.gradle.api.publish.maven.*
+import org.gradle.kotlin.dsl.*
 import org.gradle.plugins.signing.*
 import java.net.*
 
@@ -56,6 +57,11 @@
             password = project.getSensitiveProperty("libs.sonatype.password")
         }
     }
+
+    // Something that's easy to "clean" for development, not mavenLocal
+    rh.maven("${project.rootProject.buildDir}/repo") {
+        name = "buildRepo"
+    }
 }
 
 fun signPublicationIfKeyPresent(project: Project, publication: MavenPublication) {
diff --git a/buildSrc/src/main/kotlin/SourceSets.kt b/buildSrc/src/main/kotlin/SourceSets.kt
new file mode 100644
index 0000000..3ad1dd4
--- /dev/null
+++ b/buildSrc/src/main/kotlin/SourceSets.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import org.jetbrains.kotlin.gradle.plugin.*
+
+fun KotlinSourceSet.configureMultiplatform() {
+    val srcDir = if (name.endsWith("Main")) "src" else "test"
+    val platform = name.dropLast(4)
+    kotlin.srcDir("$platform/$srcDir")
+    if (name == "jvmMain") {
+        resources.srcDir("$platform/resources")
+    } else if (name == "jvmTest") {
+        resources.srcDir("$platform/test-resources")
+    }
+    languageSettings {
+        optInAnnotations.forEach { optIn(it) }
+        progressiveMode = true
+    }
+}
diff --git a/buildSrc/src/main/kotlin/UnpackAar.kt b/buildSrc/src/main/kotlin/UnpackAar.kt
index b3152d7..afe2627 100644
--- a/buildSrc/src/main/kotlin/UnpackAar.kt
+++ b/buildSrc/src/main/kotlin/UnpackAar.kt
@@ -2,18 +2,49 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
+import org.gradle.api.*
 import org.gradle.api.artifacts.transform.InputArtifact
 import org.gradle.api.artifacts.transform.TransformAction
 import org.gradle.api.artifacts.transform.TransformOutputs
 import org.gradle.api.artifacts.transform.TransformParameters
+import org.gradle.api.attributes.*
 import org.gradle.api.file.FileSystemLocation
 import org.gradle.api.provider.Provider
+import org.gradle.kotlin.dsl.*
 import java.io.File
 import java.nio.file.Files
 import java.util.zip.ZipEntry
 import java.util.zip.ZipFile
 
-// TODO move back to kotlinx-coroutines-play-services when it's migrated to the kts
+// Attributes used by aar dependencies
+val artifactType = Attribute.of("artifactType", String::class.java)
+val unpackedAar = Attribute.of("unpackedAar", Boolean::class.javaObjectType)
+
+fun Project.configureAar() = configurations.configureEach {
+    afterEvaluate {
+        if (isCanBeResolved && !isCanBeConsumed) {
+            attributes.attribute(unpackedAar, true) // request all AARs to be unpacked
+        }
+    }
+}
+
+fun DependencyHandlerScope.configureAarUnpacking() {
+    attributesSchema {
+        attribute(unpackedAar)
+    }
+
+    artifactTypes {
+        create("aar") {
+            attributes.attribute(unpackedAar, false)
+        }
+    }
+
+    registerTransform(UnpackAar::class.java) {
+        from.attribute(unpackedAar, false).attribute(artifactType, "aar")
+        to.attribute(unpackedAar, true).attribute(artifactType, "jar")
+    }
+}
+
 @Suppress("UnstableApiUsage")
 abstract class UnpackAar : TransformAction<TransformParameters.None> {
     @get:InputArtifact
diff --git a/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts
new file mode 100644
index 0000000..f00a0b3
--- /dev/null
+++ b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import ru.vyarus.gradle.plugin.animalsniffer.*
+
+configure(subprojects) {
+    // Skip JDK 8 projects or unpublished ones
+    if (!shouldSniff()) return@configure
+    apply(plugin = "ru.vyarus.animalsniffer")
+    project.plugins.withType(JavaPlugin::class.java) {
+        configure<AnimalSnifferExtension> {
+            sourceSets = listOf((project.extensions.getByName("sourceSets") as SourceSetContainer).getByName("main"))
+        }
+        val signature: Configuration by configurations
+        dependencies {
+            signature("net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature")
+            signature("org.codehaus.mojo.signature:java17:1.0@signature")
+        }
+    }
+}
+
+fun Project.shouldSniff(): Boolean {
+    // Skip all non-JVM projects
+    if (platformOf(project) != "jvm") return false
+    val name = project.name
+    if (name in unpublished || name in sourceless || name in androidNonCompatibleProjects) return false
+    return true
+}
diff --git a/buildSrc/src/main/kotlin/bom-conventions.gradle.kts b/buildSrc/src/main/kotlin/bom-conventions.gradle.kts
new file mode 100644
index 0000000..45f30ed
--- /dev/null
+++ b/buildSrc/src/main/kotlin/bom-conventions.gradle.kts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import org.gradle.kotlin.dsl.*
+import org.jetbrains.kotlin.gradle.dsl.*
+
+
+configure(subprojects.filter { it.name !in unpublished }) {
+    if (name == "kotlinx-coroutines-bom" || name == "kotlinx.coroutines") return@configure
+    if (isMultiplatform) {
+        kotlinExtension.sourceSets.getByName("jvmMain").dependencies {
+            api(project.dependencies.platform(project(":kotlinx-coroutines-bom")))
+        }
+    } else {
+        dependencies {
+            "api"(platform(project(":kotlinx-coroutines-bom")))
+        }
+    }
+}
diff --git a/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
index c7744f8..90847f4 100644
--- a/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
@@ -11,8 +11,8 @@
 }
 
 java {
-    sourceCompatibility = JavaVersion.VERSION_1_6
-    targetCompatibility = JavaVersion.VERSION_1_6
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
 }
 
 dependencies {
diff --git a/buildSrc/src/main/kotlin/kover-conventions.gradle.kts b/buildSrc/src/main/kotlin/kover-conventions.gradle.kts
new file mode 100644
index 0000000..052e2bb
--- /dev/null
+++ b/buildSrc/src/main/kotlin/kover-conventions.gradle.kts
@@ -0,0 +1,54 @@
+import kotlinx.kover.api.*
+import kotlinx.kover.tasks.*
+
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+apply(plugin = "kover")
+
+val notCovered = sourceless + internal + unpublished
+
+val expectedCoverage = mutableMapOf(
+    // These have lower coverage in general, it can be eventually fixed
+    "kotlinx-coroutines-swing" to 70, // awaitFrame is not tested
+    "kotlinx-coroutines-javafx" to 39, // JavaFx is not tested on TC because its graphic subsystem cannot be initialized in headless mode
+
+    // Reactor has lower coverage in general due to various fatal error handling features
+    "kotlinx-coroutines-reactor" to 75)
+
+extensions.configure<KoverExtension> {
+    disabledProjects = notCovered
+    /*
+     * Is explicitly enabled on TC in a separate build step.
+     * Examples:
+     * ./gradlew :p:check -- doesn't verify coverage
+     * ./gradlew :p:check -Pkover.enabled=true -- verifies coverage
+     * ./gradlew :p:koverReport -Pkover.enabled=true -- generates report
+     */
+    isDisabled = !(properties["kover.enabled"]?.toString()?.toBoolean() ?: false)
+    // TODO remove when updating Kover to version 0.5.x
+    intellijEngineVersion.set("1.0.657")
+}
+
+subprojects {
+    val projectName = name
+    if (projectName in notCovered) return@subprojects
+    tasks.withType<KoverVerificationTask> {
+        rule {
+            bound {
+                /*
+                 * 85 is our baseline that we aim to raise to 90+.
+                 * Missing coverage is typically due to bugs in the agent
+                 * (e.g. signatures deprecated with an error are counted),
+                 * sometimes it's various diagnostic `toString` or `catch` for OOMs/VerificationErrors,
+                 * but some places are definitely worth visiting.
+                 */
+                minValue = expectedCoverage[projectName] ?: 85 // COVERED_LINES_PERCENTAGE
+            }
+        }
+    }
+
+    tasks.withType<KoverHtmlReportTask> {
+        htmlReportDir.set(file(rootProject.buildDir.toString() + "/kover/" + project.name + "/html"))
+    }
+}
diff --git a/bump-version.sh b/bump-version.sh
index ae0fc0b..e49910f 100755
--- a/bump-version.sh
+++ b/bump-version.sh
@@ -21,6 +21,7 @@
 update_version "kotlinx-coroutines-test/README.md"
 update_version "ui/coroutines-guide-ui.md"
 update_version "gradle.properties"
+update_version "integration-test/gradle.properties"
 
 # Escape dots, e.g. 1.0.0 -> 1\.0\.0
 escaped_old_version=$(echo $old_version | sed s/[.]/\\\\./g)
diff --git a/coroutines-guide.md b/coroutines-guide.md
index 3b4707c..3cc035a 100644
--- a/coroutines-guide.md
+++ b/coroutines-guide.md
@@ -1,14 +1,3 @@
 The main coroutines guide has moved to the [docs folder](docs/topics/coroutines-guide.md) and split up into smaller documents.
 
-## Table of contents
-
-<!--- TOC_REF docs/topics/coroutines-basics.md -->
-<!--- TOC_REF docs/topics/cancellation-and-timeouts.md -->
-<!--- TOC_REF docs/topics/composing-suspending-functions.md -->
-<!--- TOC_REF docs/topics/coroutine-context-and-dispatchers.md -->
-<!--- TOC_REF docs/topics/flow.md -->
-<!--- TOC_REF docs/topics/channels.md -->
-<!--- TOC_REF docs/topics/exception-handling.md -->
-<!--- TOC_REF docs/topics/shared-mutable-state-and-concurrency.md -->
-<!--- TOC_REF docs/topics/select-expression.md -->
-<!--- END -->
+It is recommended to read the guide on the [kotlinlang website](https://kotlinlang.org/docs/coroutines-guide.html), with proper HTML formatting and runnable samples.
diff --git a/docs/images/coroutine-breakpoint.png b/docs/images/coroutine-breakpoint.png
index b547e77..d0e34e8 100644
--- a/docs/images/coroutine-breakpoint.png
+++ b/docs/images/coroutine-breakpoint.png
Binary files differ
diff --git a/docs/images/coroutine-idea-debugging-1.png b/docs/images/coroutine-idea-debugging-1.png
index 0afe992..c824307 100644
--- a/docs/images/coroutine-idea-debugging-1.png
+++ b/docs/images/coroutine-idea-debugging-1.png
Binary files differ
diff --git a/docs/images/flow-breakpoint.png b/docs/images/flow-breakpoint.png
index aa98e18..a7a38cc 100644
--- a/docs/images/flow-breakpoint.png
+++ b/docs/images/flow-breakpoint.png
Binary files differ
diff --git a/docs/images/flow-build-project.png b/docs/images/flow-build-project.png
index 2218621..12221c7 100644
--- a/docs/images/flow-build-project.png
+++ b/docs/images/flow-build-project.png
Binary files differ
diff --git a/docs/images/flow-debug-project.png b/docs/images/flow-debug-project.png
index 98d392e..f5b20bd 100644
--- a/docs/images/flow-debug-project.png
+++ b/docs/images/flow-debug-project.png
Binary files differ
diff --git a/docs/topics/cancellation-and-timeouts.md b/docs/topics/cancellation-and-timeouts.md
index 5221db9..0cbafb5 100644
--- a/docs/topics/cancellation-and-timeouts.md
+++ b/docs/topics/cancellation-and-timeouts.md
@@ -103,6 +103,42 @@
 main: Now I can quit.
 -->
 
+The same problem can be observed by catching a [CancellationException] and not rethrowing it:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+    val job = launch(Dispatchers.Default) {
+        repeat(5) { i ->
+            try {
+                // print a message twice a second
+                println("job: I'm sleeping $i ...")
+                delay(500)
+            } catch (e: Exception) {
+                // log the exception
+                println(e)
+            }
+        }
+    }
+    delay(1300L) // delay a bit
+    println("main: I'm tired of waiting!")
+    job.cancelAndJoin() // cancels the job and waits for its completion
+    println("main: Now I can quit.")
+//sampleEnd    
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt).
+>
+{type="note"}
+
+While catching `Exception` is an anti-pattern, this issue may surface in more subtle ways, like when using the
+[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) function,
+which does not rethrow [CancellationException].
+
 ## Making computation code cancellable
 
 There are two approaches to making computation code cancellable. The first one is to periodically 
@@ -137,7 +173,7 @@
 ```
 {kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
 >
 {type="note"}
 
@@ -154,8 +190,8 @@
 
 ## 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
+Cancellable suspending functions throw [CancellationException] on cancellation, which can be handled in 
+the usual way. For example, the `try {...} finally {...}` expression and Kotlin's `use` function execute their
 finalization actions normally when a coroutine is cancelled:
 
 ```kotlin
@@ -182,7 +218,7 @@
 ```
 {kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
 >
 {type="note"}
 
@@ -237,7 +273,7 @@
 ```
 {kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
 >
 {type="note"}
 
@@ -275,7 +311,7 @@
 ```
 {kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt).
 >
 {type="note"}
 
@@ -318,7 +354,7 @@
 ```
 {kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
 >
 {type="note"}
 
@@ -378,7 +414,7 @@
 ```
 {kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
 >
 {type="note"}
 
@@ -387,13 +423,13 @@
 If you run the above code you'll see that it does not always print zero, though it may depend on the timings 
 of your machine you may need to tweak timeouts in this example to actually see non-zero values. 
 
-> Note, that incrementing and decrementing `acquired` counter here from 100K coroutines is completely safe,
-> since it always happens from the same main thread. More on that will be explained in the next chapter
+> Note that incrementing and decrementing `acquired` counter here from 100K coroutines is completely safe,
+> since it always happens from the same main thread. More on that will be explained in the chapter
 > on coroutine context.
 > 
 {type="note"}
 
-To workaround this problem you can store a reference to the resource in the variable as opposed to returning it 
+To work around this problem you can store a reference to the resource in the variable as opposed to returning it 
 from the `withTimeout` block. 
 
 ```kotlin
@@ -431,7 +467,7 @@
 ```
 {kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt).
 >
 {type="note"}
 
@@ -444,18 +480,18 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[cancelAndJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-and-join.html
-[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
-[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
-[isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
-[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
-[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
+[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[cancelAndJoin]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-and-join.html
+[Job.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
+[Job.join]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[CancellationException]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
+[yield]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
+[isActive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[NonCancellable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
+[withTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
+[withTimeoutOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
 
 <!--- END -->
diff --git a/docs/topics/channels.md b/docs/topics/channels.md
index 7f41eae..3b8b11f 100644
--- a/docs/topics/channels.md
+++ b/docs/topics/channels.md
@@ -573,6 +573,7 @@
 import kotlinx.coroutines.*
 import kotlinx.coroutines.channels.*
 
+//sampleStart
 fun main() = runBlocking<Unit> {
     val tickerChannel = ticker(delayMillis = 100, initialDelayMillis = 0) // create ticker channel
     var nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
@@ -596,7 +597,9 @@
 
     tickerChannel.cancel() // indicate that no more elements are needed
 }
+//sampleEnd
 ```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
 > You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt).
 >
@@ -624,26 +627,26 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[kotlin.coroutines.CoroutineContext.cancelChildren]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-children.html
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[runBlocking]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[kotlin.coroutines.CoroutineContext.cancelChildren]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-children.html
+[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
 
 <!--- INDEX kotlinx.coroutines.channels -->
 
-[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
-[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
-[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
-[SendChannel.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/close.html
-[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
-[consumeEach]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/consume-each.html
-[Channel()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel.html
-[ticker]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/ticker.html
-[ReceiveChannel.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/cancel.html
-[TickerMode.FIXED_DELAY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-ticker-mode/-f-i-x-e-d_-d-e-l-a-y/index.html
+[Channel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+[SendChannel.send]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
+[ReceiveChannel.receive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
+[SendChannel.close]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/close.html
+[produce]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
+[consumeEach]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/consume-each.html
+[Channel()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel.html
+[ticker]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/ticker.html
+[ReceiveChannel.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/cancel.html
+[TickerMode.FIXED_DELAY]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-ticker-mode/-f-i-x-e-d_-d-e-l-a-y/index.html
 
 <!--- INDEX kotlinx.coroutines.selects -->
 
-[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
+[select]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
 
 <!--- END -->
diff --git a/docs/topics/compatibility.md b/docs/topics/compatibility.md
index 9744811..9570f19 100644
--- a/docs/topics/compatibility.md
+++ b/docs/topics/compatibility.md
@@ -75,7 +75,7 @@
 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).
+All API annotations are [kotlin.Experimental](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-experimental/).
 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.
 
@@ -117,13 +117,13 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines.flow -->
 
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+[Flow]: https://kotlinlang.org/api/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
+[ExperimentalCoroutinesApi]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html
+[FlowPreview]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-flow-preview/index.html
+[ObsoleteCoroutinesApi]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-obsolete-coroutines-api/index.html
+[InternalCoroutinesApi]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-internal-coroutines-api/index.html
 
 <!--- END -->
diff --git a/docs/topics/composing-suspending-functions.md b/docs/topics/composing-suspending-functions.md
index e244d8c..8ed7336 100644
--- a/docs/topics/composing-suspending-functions.md
+++ b/docs/topics/composing-suspending-functions.md
@@ -402,15 +402,15 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
-[CoroutineStart.LAZY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-l-a-z-y/index.html
-[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
-[Job.start]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html
-[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
+[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[CoroutineStart.LAZY]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-l-a-z-y/index.html
+[Deferred.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
+[Job.start]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html
+[GlobalScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[_coroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
 
 <!--- END -->
diff --git a/docs/topics/coroutine-context-and-dispatchers.md b/docs/topics/coroutine-context-and-dispatchers.md
index 9648214..32d1891 100644
--- a/docs/topics/coroutine-context-and-dispatchers.md
+++ b/docs/topics/coroutine-context-and-dispatchers.md
@@ -148,7 +148,7 @@
 The **Debug** tool window contains the **Coroutines** tab. In this tab, you can find information about both currently running and suspended coroutines. 
 The coroutines are grouped by the dispatcher they are running on.
 
-![Debugging coroutines](coroutine-idea-debugging-1.png)
+![Debugging coroutines](coroutine-idea-debugging-1.png){width=700}
 
 With the coroutine debugger, you can:
 * Check the state of each coroutine.
@@ -306,7 +306,7 @@
 
 1. When a different scope is explicitly specified when launching a coroutine (for example, `GlobalScope.launch`), 
    then it does not inherit a `Job` from the parent scope.
-2. When a different `Job` object is passed as the context for the new coroutine (as show in the example below),
+2. When a different `Job` object is passed as the context for the new coroutine (as shown in the example below),
    then it overrides the `Job` of the parent scope.
    
 In both cases, the launched coroutine is not tied to the scope it was launched from and operates independently.
@@ -334,8 +334,8 @@
     }
     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?")
+    delay(1000) // delay the main thread for a second to see what happens
 //sampleEnd
 }
 ```
@@ -350,8 +350,8 @@
 ```text
 job1: I run in my own Job and execute independently!
 job2: I am a child of the request coroutine
-job1: I am not affected by cancellation of the request
 main: Who has survived request cancellation?
+job1: I am not affected by cancellation of the request
 ```
 
 <!--- TEST -->
@@ -658,28 +658,28 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
-[ExecutorCoroutineDispatcher.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-executor-coroutine-dispatcher/close.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
-[CoroutineScope.coroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
-[CoroutineScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
-[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
-[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
-[asContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-context-element.html
-[ensurePresent]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-present.html
-[ThreadContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[CoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[Dispatchers.Unconfined]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
+[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[newSingleThreadContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
+[ExecutorCoroutineDispatcher.close]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-executor-coroutine-dispatcher/close.html
+[runBlocking]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[DEBUG_PROPERTY_NAME]: https://kotlinlang.org/api/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://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[isActive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
+[CoroutineScope.coroutineContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html
+[Job.join]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[CoroutineName]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
+[CoroutineScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
+[MainScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
+[Dispatchers.Main]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
+[asContextElement]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-context-element.html
+[ensurePresent]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-present.html
+[ThreadContextElement]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html
 
 <!--- END -->
diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md
index 5d9d0e6..ad33dcf 100644
--- a/docs/topics/coroutines-basics.md
+++ b/docs/topics/coroutines-basics.md
@@ -75,7 +75,7 @@
 which delimits the lifetime of the coroutine. The above example shows that [runBlocking] establishes the corresponding
 scope and that is why the previous example waits until `World!` is printed after a second's delay and only then exits.
 
-In the real application, you will be launching a lot of coroutines. Structured concurrency ensures that they are not
+In a real application, you will be launching a lot of coroutines. Structured concurrency ensures that they are not
 lost and do not leak. An outer scope cannot complete until all its children coroutines complete. 
 Structured concurrency also ensures that any errors in the code are properly reported and are never lost.  
 
@@ -245,14 +245,17 @@
 
 <!--- TEST -->
 
-## Coroutines ARE light-weight
+## Coroutines are light-weight
 
-Run the following code:
+Coroutines are less resource-intensive than JVM threads. Code that exhausts the
+JVM's available memory when using threads can be expressed using coroutines
+without hitting resource limits. For example, the following code launches
+100000 distinct coroutines that each wait 5 seconds and then print a period
+('.') while consuming very little memory:
 
 ```kotlin
 import kotlinx.coroutines.*
 
-//sampleStart
 fun main() = runBlocking {
     repeat(100_000) { // launch a lot of coroutines
         launch {
@@ -261,8 +264,9 @@
         }
     }
 }
-//sampleEnd
 ```
+<!-- While coroutines do have a smaller memory footprint than threads, this
+example will exhaust the playground's heap memory; don't make it runnable. -->
 
 > You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
 >
@@ -270,19 +274,18 @@
 
 <!--- TEST lines.size == 1 && lines[0] == ".".repeat(100_000) -->
 
-It launches 100K coroutines and, after 5 seconds, each coroutine prints a dot. 
-
-Now, try that with threads (remove `runBlocking`, replace `launch` with `thread`, and replace `delay` with `Thread.sleep`). 
-What would happen? (Most likely your code will produce some sort of out-of-memory error)
+If you write the same program using threads (remove `runBlocking`, replace
+`launch` with `thread`, and replace `delay` with `Thread.sleep`), it will
+likely consume too much memory and throw an out-of-memory error.
 
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[runBlocking]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[_coroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
 
 <!--- END -->
diff --git a/docs/topics/coroutines-guide.md b/docs/topics/coroutines-guide.md
index 0c6432f..73c4c1a 100644
--- a/docs/topics/coroutines-guide.md
+++ b/docs/topics/coroutines-guide.md
@@ -33,6 +33,6 @@
 
 * [Guide to UI programming with coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md)
 * [Coroutines design document (KEEP)](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md)
-* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines)
+* [Full kotlinx.coroutines API reference](https://kotlinlang.org/api/kotlinx.coroutines/)
 * [Best practices for coroutines in Android](https://developer.android.com/kotlin/coroutines/coroutines-best-practices)
 * [Additional Android resources for Kotlin coroutines and flow](https://developer.android.com/kotlin/coroutines/additional-resources)
diff --git a/docs/topics/debug-coroutines-with-idea.md b/docs/topics/debug-coroutines-with-idea.md
index e59075e..2541c92 100644
--- a/docs/topics/debug-coroutines-with-idea.md
+++ b/docs/topics/debug-coroutines-with-idea.md
@@ -16,11 +16,11 @@
 
     The `src` directory contains Kotlin source files and resources. The `main.kt` file contains sample code that will print `Hello World!`.
 
-2. Change code in the `main()` function:
+3. Change code in the `main()` function:
 
-    * Use the [`runBlocking()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block to wrap a coroutine.
-    * Use the [`async()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) function to create coroutines that compute deferred values `a` and `b`.
-    * Use the [`await()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html) function to await the computation result.
+    * Use the [`runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block to wrap a coroutine.
+    * Use the [`async()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) function to create coroutines that compute deferred values `a` and `b`.
+    * Use the [`await()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html) function to await the computation result.
     * Use the [`println()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/println.html) function to print computing status and the result of multiplication to the output.
 
     ```kotlin
@@ -61,7 +61,7 @@
 
     ![Debug the coroutine](coroutine-debug-1.png)
 
-3. Resume the debugger session by clicking **Resume program** in the **Debug** tool window:
+3. Resume the debugger session by clicking **Resume Program** in the **Debug** tool window:
 
     ![Debug the coroutine](coroutine-debug-2.png)
     
@@ -70,7 +70,7 @@
     * The second coroutine is calculating the `a` value – it has the **RUNNING** status.
     * The third coroutine has the **CREATED** status and isn’t calculating the value of `b`.
 
-4. Resume the debugger session by clicking **Resume program** in the **Debug** tool window:
+4. Resume the debugger session by clicking **Resume Program** in the **Debug** tool window:
 
     ![Build a console application](coroutine-debug-3.png)
 
diff --git a/docs/topics/debug-flow-with-idea.md b/docs/topics/debug-flow-with-idea.md
index 745dcb1..b769e79 100644
--- a/docs/topics/debug-flow-with-idea.md
+++ b/docs/topics/debug-flow-with-idea.md
@@ -10,7 +10,7 @@
 
 ## Create a Kotlin flow
 
-Create a Kotlin [flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html) with a slow emitter and a slow collector:
+Create a Kotlin [flow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html) with a slow emitter and a slow collector:
 
 1. Open a Kotlin project in IntelliJ IDEA. If you don't have a project, [create one](jvm-get-started.md#create-an-application).
 
@@ -18,10 +18,10 @@
 
     The `src` directory contains Kotlin source files and resources. The `main.kt` file contains sample code that will print `Hello World!`.
 
-2. Create the `simple()` function that returns a flow of three numbers:
+3. Create the `simple()` function that returns a flow of three numbers:
 
-    * Use the [`delay()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html) function to imitate CPU-consuming blocking code. It suspends the coroutine for 100 ms without blocking the thread.
-    * Produce the values in the `for` loop using the [`emit()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow-collector/emit.html) function.
+    * Use the [`delay()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html) function to imitate CPU-consuming blocking code. It suspends the coroutine for 100 ms without blocking the thread.
+    * Produce the values in the `for` loop using the [`emit()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow-collector/emit.html) function.
 
     ```kotlin
     import kotlinx.coroutines.*
@@ -36,11 +36,11 @@
     }
     ```
 
-3. Change the code in the `main()` function:
+4. Change the code in the `main()` function:
 
-    * Use the [`runBlocking()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block to wrap a coroutine.
-    * Collect the emitted values using the [`collect()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html) function.
-    * Use the [`delay()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html) function to imitate CPU-consuming code. It suspends the coroutine for 300 ms without blocking the thread.
+    * Use the [`runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block to wrap a coroutine.
+    * Collect the emitted values using the [`collect()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html) function.
+    * Use the [`delay()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html) function to imitate CPU-consuming code. It suspends the coroutine for 300 ms without blocking the thread.
     * Print the collected value from the flow using the [`println()`](https://kotlinlang.org/api/latest/jvm/stdlib/stdlib/kotlin.io/println.html) function.
 
     ```kotlin
@@ -53,13 +53,13 @@
     }
     ```
 
-4. Build the code by clicking **Build Project**.
+5. Build the code by clicking **Build Project**.
 
     ![Build an application](flow-build-project.png)
 
 ## Debug the coroutine
 
-1. Set a breakpoint at the at the line where the `emit()` function is called:
+1. Set a breakpoint at the line where the `emit()` function is called:
 
     ![Build a console application](flow-breakpoint.png)
 
@@ -74,7 +74,7 @@
 
     ![Debug the coroutine](flow-debug-1.png)
 
-3. Resume the debugger session by clicking **Resume program** in the **Debug** tool window. The program stops at the same breakpoint.
+3. Resume the debugger session by clicking **Resume Program** in the **Debug** tool window. The program stops at the same breakpoint.
 
     ![Debug the coroutine](flow-resume-debug.png)
 
@@ -88,7 +88,7 @@
 
 2. Enhance the code to run the emitter and collector concurrently:
 
-    * Add a call to the [`buffer()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html) function to run the emitter and collector concurrently. `buffer()` stores emitted values and runs the flow collector in a separate coroutine. 
+    * Add a call to the [`buffer()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html) function to run the emitter and collector concurrently. `buffer()` stores emitted values and runs the flow collector in a separate coroutine. 
  
     ```kotlin
     fun main() = runBlocking<Unit> {
@@ -101,7 +101,7 @@
     }
     ```
 
-4. Build the code by clicking **Build Project**.
+3. Build the code by clicking **Build Project**.
 
 ## Debug a Kotlin flow with two coroutines
 
@@ -117,10 +117,10 @@
     The `buffer()` function buffers emitted values from the flow.
     The emitter coroutine has the **RUNNING** status, and the collector coroutine has the **SUSPENDED** status.
 
-2. Resume the debugger session by clicking **Resume program** in the **Debug** tool window.
+3. Resume the debugger session by clicking **Resume Program** in the **Debug** tool window.
 
     ![Debugging coroutines](flow-debug-4.png)
 
     Now the collector coroutine has the **RUNNING** status, while the emitter coroutine has the **SUSPENDED** status.
 
-    You can dig deeper into each coroutine to debug your code.
\ No newline at end of file
+    You can dig deeper into each coroutine to debug your code.
diff --git a/docs/topics/debugging.md b/docs/topics/debugging.md
index 5ff4d54..a2c32e0 100644
--- a/docs/topics/debugging.md
+++ b/docs/topics/debugging.md
@@ -7,7 +7,6 @@
 * [Stacktrace recovery](#stacktrace-recovery)
   * [Stacktrace recovery machinery](#stacktrace-recovery-machinery)
 * [Debug agent](#debug-agent)
-  * [Debug agent and Android](#debug-agent-and-android)
 * [Android optimization](#android-optimization)
 
 <!--- END -->
@@ -77,12 +76,6 @@
 
 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;
@@ -98,18 +91,18 @@
 ## Android optimization
 
 In optimized (release) builds with R8 version 1.6.0 or later both 
-[Debugging mode](../../docs/debugging.md#debug-mode) and 
-[Stacktrace recovery](../../docs/debugging.md#stacktrace-recovery) 
+[Debugging mode](debugging.md#debug-mode) and 
+[Stacktrace recovery](debugging.md#stacktrace-recovery) 
 are permanently turned off. 
 For more details see ["Optimization" section for Android](../../ui/kotlinx-coroutines-android/README.md#optimization). 
 
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
-[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
-[CopyableThrowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/index.html
-[CopyableThrowable.createCopy]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/create-copy.html
+[DEBUG_PROPERTY_NAME]: https://kotlinlang.org/api/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://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
+[CopyableThrowable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/index.html
+[CopyableThrowable.createCopy]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/create-copy.html
 
 <!--- MODULE kotlinx-coroutines-debug -->
 <!--- END -->
diff --git a/docs/topics/exception-handling.md b/docs/topics/exception-handling.md
index 35e645f..8bf8c10 100644
--- a/docs/topics/exception-handling.md
+++ b/docs/topics/exception-handling.md
@@ -15,7 +15,7 @@
 the former builders treat exceptions as **uncaught** exceptions, similar to Java's `Thread.uncaughtExceptionHandler`,
 while the latter are relying on the user to consume the final
 exception, for example via [await][Deferred.await] or [receive][ReceiveChannel.receive] 
-([produce] and [receive][ReceiveChannel.receive] are covered later in [Channels](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/channels.md) section).
+([produce] and [receive][ReceiveChannel.receive] are covered in [Channels](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/channels.md) section).
 
 It can be demonstrated by a simple example that creates root coroutines using the [GlobalScope]:
 
@@ -28,6 +28,7 @@
 ```kotlin
 import kotlinx.coroutines.*
 
+//sampleStart
 @OptIn(DelicateCoroutinesApi::class)
 fun main() = runBlocking {
     val job = GlobalScope.launch { // root coroutine with launch
@@ -47,7 +48,9 @@
         println("Caught ArithmeticException")
     }
 }
+//sampleEnd
 ```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
 
 > You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt).
 >
@@ -68,19 +71,13 @@
 ## CoroutineExceptionHandler
 
 It is possible to customize the default behavior of printing **uncaught** exceptions to the console.
-[CoroutineExceptionHandler] context element on a _root_ coroutine can be used as generic `catch` block for
+[CoroutineExceptionHandler] context element on a _root_ coroutine can be used as a generic `catch` block for
 this root coroutine and all its children where custom exception handling may take place.
 It is similar to [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
 You cannot recover from the exception in the `CoroutineExceptionHandler`. The coroutine had already completed
 with the corresponding exception when the handler is called. Normally, the handler is used to
 log the exception, show some kind of error message, terminate, and/or restart the application.
 
-On JVM it is possible to redefine global exception handler for all coroutines by registering [CoroutineExceptionHandler] via
-[`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
-Global exception handler is similar to 
-[`Thread.defaultUncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setDefaultUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)) 
-which is used when no more specific handlers are registered.
-On Android, `uncaughtExceptionPreHandler` is installed as a global coroutine exception handler.
 
 `CoroutineExceptionHandler` is invoked only on **uncaught** exceptions &mdash; exceptions that were not handled in any other way.
 In particular, all _children_ coroutines (coroutines created in the context of another [Job]) delegate handling of
@@ -349,7 +346,7 @@
 
 A good example of such a requirement is a UI component with the job defined in its scope. If any of the UI's child tasks
 have failed, it is not always necessary to cancel (effectively kill) the whole UI component,
-but if UI component is destroyed (and its job is cancelled), then it is necessary to fail all child jobs as their results are no longer needed.
+but if the UI component is destroyed (and its job is cancelled), then it is necessary to cancel all child jobs as their results are no longer needed.
 
 Another example is a server process that spawns multiple child jobs and needs to _supervise_
 their execution, tracking their failures and only restarting the failed ones.
@@ -509,25 +506,25 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
-[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
-[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
-[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[SupervisorJob()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html
-[Job()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job.html
-[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
-[_supervisorScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
+[CancellationException]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
+[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[Deferred.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
+[GlobalScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
+[CoroutineExceptionHandler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[Job.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
+[runBlocking]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[SupervisorJob()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html
+[Job()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job.html
+[_coroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
+[_supervisorScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
 
 <!--- INDEX kotlinx.coroutines.channels -->
 
-[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
-[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
-[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
+[actor]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
+[produce]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
+[ReceiveChannel.receive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
 
 <!--- END -->
diff --git a/docs/topics/flow.md b/docs/topics/flow.md
index 311411f..538a9d6 100644
--- a/docs/topics/flow.md
+++ b/docs/topics/flow.md
@@ -1832,9 +1832,9 @@
 <!-- stdlib references -->
 
 [collections]: https://kotlinlang.org/docs/reference/collections-overview.html 
-[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index.html 
+[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/ 
 [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]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/
 [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
@@ -1843,54 +1843,54 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[ensureActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-active.html
-[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
+[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[withTimeoutOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
+[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[Dispatchers.Main]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
+[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[CoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[runBlocking]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Job.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
+[Job.join]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[ensureActive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-active.html
+[CancellationException]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
 
 <!--- INDEX kotlinx.coroutines.flow -->
 
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
-[_flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
-[FlowCollector.emit]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow-collector/emit.html
-[collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html
-[flowOf]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-of.html
-[map]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html
-[filter]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/filter.html
-[transform]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/transform.html
-[take]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/take.html
-[toList]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html
-[toSet]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-set.html
-[first]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/first.html
-[single]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/single.html
-[reduce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/reduce.html
-[fold]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/fold.html
-[flowOn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-on.html
-[buffer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html
-[conflate]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/conflate.html
-[collectLatest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect-latest.html
-[zip]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/zip.html
-[combine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/combine.html
-[onEach]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-each.html
-[flatMapConcat]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-concat.html
-[flattenConcat]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flatten-concat.html
-[flatMapMerge]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-merge.html
-[flattenMerge]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flatten-merge.html
-[DEFAULT_CONCURRENCY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-d-e-f-a-u-l-t_-c-o-n-c-u-r-r-e-n-c-y.html
-[flatMapLatest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-latest.html
-[catch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/catch.html
-[onCompletion]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-completion.html
-[launchIn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/launch-in.html
-[IntRange.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/as-flow.html
-[cancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/cancellable.html
+[Flow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+[_flow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
+[FlowCollector.emit]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow-collector/emit.html
+[collect]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html
+[flowOf]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-of.html
+[map]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html
+[filter]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/filter.html
+[transform]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/transform.html
+[take]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/take.html
+[toList]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html
+[toSet]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-set.html
+[first]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/first.html
+[single]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/single.html
+[reduce]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/reduce.html
+[fold]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/fold.html
+[flowOn]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-on.html
+[buffer]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html
+[conflate]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/conflate.html
+[collectLatest]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect-latest.html
+[zip]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/zip.html
+[combine]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/combine.html
+[onEach]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-each.html
+[flatMapConcat]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-concat.html
+[flattenConcat]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flatten-concat.html
+[flatMapMerge]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-merge.html
+[flattenMerge]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flatten-merge.html
+[DEFAULT_CONCURRENCY]: https://kotlinlang.org/api/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://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-latest.html
+[catch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/catch.html
+[onCompletion]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-completion.html
+[launchIn]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/launch-in.html
+[IntRange.asFlow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/as-flow.html
+[cancellable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/cancellable.html
 
 <!--- END -->
diff --git a/docs/topics/select-expression.md b/docs/topics/select-expression.md
index 3d20ff3..f305573 100644
--- a/docs/topics/select-expression.md
+++ b/docs/topics/select-expression.md
@@ -213,7 +213,7 @@
 
 <!--- TEST -->
 
-There are couple of observations to make out of it. 
+There are a 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,
@@ -228,7 +228,7 @@
 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 
+Let us write an example of a producer of integers that sends its values to a `side` channel when 
 the consumers on its primary channel cannot keep up with it:
 
 ```kotlin
@@ -490,18 +490,18 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
+[Deferred.onAwait]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
 
 <!--- INDEX kotlinx.coroutines.channels -->
 
-[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
-[ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
-[ReceiveChannel.onReceiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
-[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
-[SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
+[ReceiveChannel.receive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
+[ReceiveChannel.onReceive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
+[ReceiveChannel.onReceiveCatching]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
+[SendChannel.send]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
+[SendChannel.onSend]: https://kotlinlang.org/api/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
+[select]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
 
 <!--- END -->
diff --git a/docs/topics/shared-mutable-state-and-concurrency.md b/docs/topics/shared-mutable-state-and-concurrency.md
index 40b0134..99cc42b 100644
--- a/docs/topics/shared-mutable-state-and-concurrency.md
+++ b/docs/topics/shared-mutable-state-and-concurrency.md
@@ -9,7 +9,7 @@
 
 ## The problem
 
-Let us launch a hundred coroutines all doing the same action thousand times. 
+Let us launch a hundred coroutines all doing the same action a thousand times. 
 We'll also measure their completion time for further comparisons:
 
 ```kotlin
@@ -384,7 +384,7 @@
 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
+to get its value. The latter 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.
 
@@ -494,20 +494,20 @@
 <!--- 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
+[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[CompletableDeferred]: https://kotlinlang.org/api/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
+[Mutex]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
+[Mutex.lock]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
+[Mutex.unlock]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/unlock.html
+[withLock]: https://kotlinlang.org/api/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
+[actor]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
+[produce]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
 
 <!--- END -->
diff --git a/gradle.properties b/gradle.properties
index 26e5147..e452a07 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,18 +3,18 @@
 #
 
 # Kotlin
-version=1.5.2-SNAPSHOT
+version=1.6.4-SNAPSHOT
 group=org.jetbrains.kotlinx
-kotlin_version=1.5.30
+kotlin_version=1.6.21
 
 # Dependencies
 junit_version=4.12
 junit5_version=5.7.0
-atomicfu_version=0.16.3
-knit_version=0.3.0
+atomicfu_version=0.17.3
+knit_version=0.4.0
 html_version=0.7.2
 lincheck_version=2.14
-dokka_version=1.5.0
+dokka_version=1.6.21
 byte_buddy_version=1.10.9
 reactor_version=3.4.1
 reactive_streams_version=1.0.3
@@ -22,20 +22,21 @@
 rxjava3_version=3.0.2
 javafx_version=11.0.2
 javafx_plugin_version=0.0.8
-binary_compatibility_validator_version=0.7.0
+binary_compatibility_validator_version=0.11.0
+kover_version=0.5.0
 blockhound_version=1.0.2.RELEASE
-jna_version=5.5.0
+jna_version=5.9.0
 
 # Android versions
 android_version=4.1.1.4
 androidx_annotation_version=1.1.0
-robolectric_version=4.0.2
+robolectric_version=4.4
 baksmali_version=2.2.7
 
 # JS
 kotlin.js.compiler=both
-gradle_node_version=1.2.0
-node_version=8.9.3
+gradle_node_version=3.1.1
+node_version=10.0.0
 npm_version=5.7.1
 mocha_version=6.2.2
 mocha_headless_chrome_version=1.8.2
@@ -53,7 +54,8 @@
 
 # JS IR backend sometimes crashes with out-of-memory
 # TODO: Remove once KT-37187 is fixed
-org.gradle.jvmargs=-Xmx4g
+org.gradle.jvmargs=-Xmx3g
 
 kotlin.mpp.enableCompatibilityMetadataVariant=true
 kotlin.mpp.stability.nowarn=true
+kotlinx.atomicfu.enableIrTransformation=true
diff --git a/gradle/compile-js-multiplatform.gradle b/gradle/compile-js-multiplatform.gradle
index d6df7e4..c6fc757 100644
--- a/gradle/compile-js-multiplatform.gradle
+++ b/gradle/compile-js-multiplatform.gradle
@@ -60,7 +60,7 @@
 task populateNodeModules(type: Copy, dependsOn: compileTestJsLegacy) {
     // we must copy output that is transformed by atomicfu
     from(kotlin.js().compilations.main.output.allOutputs)
-    into "$node.nodeModulesDir/node_modules"
+    into node.nodeProjectDir.dir("node_modules")
 
     def configuration = configurations.hasProperty("jsLegacyTestRuntimeClasspath")
             ? configurations.jsLegacyTestRuntimeClasspath
diff --git a/gradle/compile-jvm-multiplatform.gradle b/gradle/compile-jvm-multiplatform.gradle
index 5e65042..88b7179 100644
--- a/gradle/compile-jvm-multiplatform.gradle
+++ b/gradle/compile-jvm-multiplatform.gradle
@@ -2,12 +2,16 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
-sourceCompatibility = 1.6
-targetCompatibility = 1.6
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
 
 kotlin {
     jvm {}
     sourceSets {
+        jvmMain.dependencies {
+            compileOnly "org.codehaus.mojo:animal-sniffer-annotations:1.20"
+        }
+
         jvmTest.dependencies {
             api "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
             // Workaround to make addSuppressed work in tests
diff --git a/gradle/dokka.gradle.kts b/gradle/dokka.gradle.kts
index 659890a..2470ded 100644
--- a/gradle/dokka.gradle.kts
+++ b/gradle/dokka.gradle.kts
@@ -37,12 +37,8 @@
             packageListUrl.set(rootProject.projectDir.toPath().resolve("site/stdlib.package.list").toUri().toURL())
         }
 
-        if (project.name != "kotlinx-coroutines-core") {
+        if (!project.isMultiplatform) {
             dependsOn(project.configurations["compileClasspath"])
-            doFirst {
-                // resolve classpath only during execution
-                classpath.from(project.configurations["compileClasspath"].files)// + project.sourceSets.main.output.files)
-            }
         }
     }
 }
@@ -66,10 +62,6 @@
             val jvmMain by getting {
                 makeLinkMapping(project.file("jvm"))
             }
-
-            configureEach {
-                classpath.from(project.configurations["jvmCompileClasspath"].files)
-            }
         }
     }
 }
diff --git a/gradle/node-js.gradle b/gradle/node-js.gradle
index 42f101c..5eddc5f 100644
--- a/gradle/node-js.gradle
+++ b/gradle/node-js.gradle
@@ -2,13 +2,13 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
-apply plugin: 'com.moowork.node'
+apply plugin: 'com.github.node-gradle.node'
 
 node {
     version = "$node_version"
     npmVersion = "$npm_version"
     download = true
-    nodeModulesDir = file(buildDir)
+    nodeProjectDir = file(buildDir)
 }
 
 // Configures testing for JS modules
@@ -25,7 +25,7 @@
     from("npm") {
         exclude 'package.json'
     }
-    into "$node.nodeModulesDir"
+    into node.nodeProjectDir
 }
 
 npmInstall.dependsOn prepareNodePackage
diff --git a/gradle/opt-in.gradle b/gradle/opt-in.gradle
deleted file mode 100644
index 22f022d..0000000
--- a/gradle/opt-in.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-ext.optInAnnotations = [
-        "kotlin.RequiresOptIn",
-        "kotlin.experimental.ExperimentalTypeInference",
-        "kotlin.ExperimentalMultiplatform",
-        "kotlinx.coroutines.DelicateCoroutinesApi",
-        "kotlinx.coroutines.ExperimentalCoroutinesApi",
-        "kotlinx.coroutines.ObsoleteCoroutinesApi",
-        "kotlinx.coroutines.InternalCoroutinesApi",
-        "kotlinx.coroutines.FlowPreview"]
diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle
index 382c674..9d41527 100644
--- a/gradle/publish-npm-js.gradle
+++ b/gradle/publish-npm-js.gradle
@@ -40,17 +40,15 @@
 
 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
-        }
+
+    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/publish.gradle b/gradle/publish.gradle
index 3a0a422..f3b1561 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -6,17 +6,18 @@
 
 // Configures publishing of Maven artifacts to Maven Central
 
-apply plugin: 'maven'
 apply plugin: 'maven-publish'
 apply plugin: 'signing'
 
 // ------------- tasks
 
-def isMultiplatform = project.name == "kotlinx-coroutines-core"
+def isMultiplatform = project.name == "kotlinx-coroutines-core" || project.name == "kotlinx-coroutines-test"
 def isBom = project.name == "kotlinx-coroutines-bom"
 
 if (!isBom) {
-    apply plugin: "com.github.johnrengelman.shadow"
+    if (project.name == "kotlinx-coroutines-debug") {
+        apply plugin: "com.github.johnrengelman.shadow"
+    }
 
     // empty xxx-javadoc.jar
     task javadocJar(type: Jar) {
@@ -44,11 +45,7 @@
         // 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
-                }
+                from components.java
                 artifact sourcesJar
             }
         }
diff --git a/gradle/test-mocha-js.gradle b/gradle/test-mocha-js.gradle
index d011eea..27d2e5b 100644
--- a/gradle/test-mocha-js.gradle
+++ b/gradle/test-mocha-js.gradle
@@ -9,7 +9,7 @@
             "mocha@$mocha_version",
             "source-map-support@$source_map_support_version",
             '--no-save']
-    if (project.hasProperty("teamcity")) args += ["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+    if (project.hasProperty("teamcity")) args.addAll(["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"])
 }
 
 def compileJsLegacy = tasks.hasProperty("compileKotlinJsLegacy")
@@ -22,9 +22,9 @@
 
 // todo: use atomicfu-transformed test files here (not critical)
 task testMochaNode(type: NodeTask, dependsOn: [compileTestJsLegacy, installDependenciesMochaNode]) {
-    script = file("$node.nodeModulesDir/node_modules/mocha/bin/mocha")
-    args = [compileTestJsLegacy.outputFile, '--require', 'source-map-support/register']
-    if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+    script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha/bin/mocha")
+    args = [compileTestJsLegacy.outputFile.path, '--require', 'source-map-support/register']
+    if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter'])
 }
 
 def jsLegacyTestTask = project.tasks.findByName('jsLegacyTest') ? jsLegacyTest : jsTest
@@ -40,8 +40,8 @@
             "kotlin@$kotlin_version",
             "kotlin-test@$kotlin_version",
             '--no-save']
-    if (project.hasProperty("teamcity")) args += [
-            "mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+    if (project.hasProperty("teamcity")) args.addAll([
+            "mocha-teamcity-reporter@$mocha_teamcity_reporter_version"])
 }
 
 def mochaChromeTestPage = file("$buildDir/test-page.html")
@@ -51,19 +51,20 @@
 }
 
 prepareMochaChrome.doLast {
+    def nodeProjDir = node.nodeProjectDir.getAsFile().get()
     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">
+            <link rel="stylesheet" href="$nodeProjDir/node_modules/mocha/mocha.css">
         </head>
         <body>
         <div id="mocha"></div>
-        <script src="$node.nodeModulesDir/node_modules/mocha/mocha.js"></script>
+        <script src="$nodeProjDir/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="$nodeProjDir/node_modules/kotlin/kotlin.js"></script>
+        <script src="$nodeProjDir/node_modules/kotlin-test/kotlin-test.js"></script>
         <script src="$compileJsLegacy.outputFile"></script>
         <script src="$compileTestJsLegacy.outputFile"></script>
         <script>mocha.run();</script>
@@ -73,9 +74,9 @@
 }
 
 task testMochaChrome(type: NodeTask, dependsOn: prepareMochaChrome) {
-    script = file("$node.nodeModulesDir/node_modules/mocha-headless-chrome/bin/start")
-    args = [compileTestJsLegacy.outputFile, '--file', mochaChromeTestPage]
-    if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+    script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha-headless-chrome/bin/start")
+    args = [compileTestJsLegacy.outputFile.path, '--file', mochaChromeTestPage]
+    if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter'])
 }
 
 // todo: Commented out because mocha-headless-chrome does not work on TeamCity
@@ -90,13 +91,13 @@
             "jsdom-global@$jsdom_global_version",
             "source-map-support@$source_map_support_version",
             '--no-save']
-    if (project.hasProperty("teamcity")) args += ["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+    if (project.hasProperty("teamcity")) args.addAll(["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"])
 }
 
 task testMochaJsdom(type: NodeTask, dependsOn: [compileTestJsLegacy, installDependenciesMochaJsdom]) {
-    script = file("$node.nodeModulesDir/node_modules/mocha/bin/mocha")
-    args = [compileTestJsLegacy.outputFile, '--require', 'source-map-support/register', '--require', 'jsdom-global/register']
-    if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+    script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha/bin/mocha")
+    args = [compileTestJsLegacy.outputFile.path, '--require', 'source-map-support/register', '--require', 'jsdom-global/register']
+    if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter'])
 }
 
 jsLegacyTestTask.dependsOn testMochaJsdom
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d7ae858..f57489c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -4,6 +4,6 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/integration-testing/README.md b/integration-testing/README.md
index 4754081..0ede9b2 100644
--- a/integration-testing/README.md
+++ b/integration-testing/README.md
@@ -1,14 +1,13 @@
 # Integration tests
 
-This is a supplementary subproject of kotlinx.coroutines that provides
-integration tests.
+This is a supplementary project that provides integration tests.
 
 The tests are the following:
-* `NpmPublicationValidator` tests that version of NPM artifact is correct and that it has neither source nor package dependencies on atomicfu
-  In order for the test to work, one needs to run gradle with `-PdryRun=true`.
-  `-PdryRun` affects `npmPublish` so that it only provides a packed publication
-  and does not in fact attempt to send the build for publication.
-* `MavenPublicationValidator` depends on the published artifacts and tests artifacts binary content and absence of atomicfu in the classpath
+* `MavenPublicationValidator` depends on the published artifacts and tests artifacts binary content and absence of atomicfu in the classpath.
+* `CoreAgentTest` checks that `kotlinx-coroutines-core` can be run as a Java agent.
 * `DebugAgentTest` checks that the coroutine debugger can be run as a Java agent.
+* `smokeTest` builds the test project that depends on coroutines.
 
-All the available tests can be run with `integration-testing:test`.
+The `integration-testing` project is expected to be in a subdirectory of the main `kotlinx.coroutines` project.
+
+To run all the available tests: `cd integration-testing` + `./gradlew check`.
diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle
index 6efa3a1..985a40e 100644
--- a/integration-testing/build.gradle
+++ b/integration-testing/build.gradle
@@ -5,7 +5,7 @@
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
 
 plugins {
-    id("kotlin-jvm-conventions")
+    id "org.jetbrains.kotlin.jvm"
 }
 
 repositories {
@@ -13,27 +13,71 @@
     mavenCentral()
 }
 
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
+}
+
 sourceSets {
-    npmTest {
+    // Test that relies on Guava to reflectively check all Throwable subclasses in coroutines
+    withGuavaTest {
         kotlin
         compileClasspath += sourceSets.test.runtimeClasspath
         runtimeClasspath += sourceSets.test.runtimeClasspath
+
+        dependencies {
+            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+            implementation 'com.google.guava:guava:31.1-jre'
+        }
     }
+    // Checks correctness of Maven publication (JAR resources) and absence of atomicfu symbols
     mavenTest {
         kotlin
         compileClasspath += sourceSets.test.runtimeClasspath
         runtimeClasspath += sourceSets.test.runtimeClasspath
+
+        dependencies {
+            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+        }
     }
+    // Checks that kotlinx-coroutines-debug can be used as -javaagent parameter
     debugAgentTest {
         kotlin
         compileClasspath += sourceSets.test.runtimeClasspath
         runtimeClasspath += sourceSets.test.runtimeClasspath
+
+        dependencies {
+            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutines_version"
+        }
     }
 
+    // Checks that kotlinx-coroutines-debug agent can self-attach dynamically to JVM as standalone dependency
+    debugDynamicAgentTest {
+        kotlin
+        compileClasspath += sourceSets.test.runtimeClasspath
+        runtimeClasspath += sourceSets.test.runtimeClasspath
+
+        dependencies {
+            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutines_version"
+        }
+    }
+
+    // Checks that kotlinx-coroutines-core can be used as -javaagent parameter
     coreAgentTest {
         kotlin
         compileClasspath += sourceSets.test.runtimeClasspath
         runtimeClasspath += sourceSets.test.runtimeClasspath
+
+        dependencies {
+            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+        }
     }
 }
 
@@ -43,68 +87,47 @@
     }
 }
 
-task npmTest(type: Test) {
-    def sourceSet = sourceSets.npmTest
-    environment "projectRoot", project.rootDir
-    environment "deployVersion", version
-    def dryRunNpm = project.properties['dryRun']
-    def doRun = dryRunNpm == "true" // so that we don't accidentally publish anything, especially before the test
-    onlyIf { doRun }
-    if (doRun) { // `onlyIf` only affects execution of the task, not the dependency subtree
-        dependsOn(project(':').getTasksByName("publishNpm", true))
-    }
+task withGuavaTest(type: Test) {
+    environment "version", coroutines_version
+    def sourceSet = sourceSets.withGuavaTest
     testClassesDirs = sourceSet.output.classesDirs
     classpath = sourceSet.runtimeClasspath
 }
 
 task mavenTest(type: Test) {
+    environment "version", coroutines_version
     def sourceSet = sourceSets.mavenTest
-    dependsOn(project(':').getTasksByName("publishToMavenLocal", true))
     testClassesDirs = sourceSet.output.classesDirs
     classpath = sourceSet.runtimeClasspath
-    // we can't depend on the subprojects because we need to test the classfiles that are published in the end.
-    // also, we can't put this in the `dependencies` block because the resolution would happen before publication.
-    def mavenTestClasspathConfiguration = project.configurations.detachedConfiguration(
-            project.dependencies.create("org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"),
-            project.dependencies.create("org.jetbrains.kotlinx:kotlinx-coroutines-android:$version"))
-
-    mavenTestClasspathConfiguration.attributes {
-        attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm)
-    }
-
-    classpath += mavenTestClasspathConfiguration
 }
 
 task debugAgentTest(type: Test) {
     def sourceSet = sourceSets.debugAgentTest
-    dependsOn(project(':kotlinx-coroutines-debug').shadowJar)
-    jvmArgs ('-javaagent:' + project(':kotlinx-coroutines-debug').shadowJar.outputs.files.getFiles()[0])
+    def coroutinesDebugJar = sourceSet.runtimeClasspath.filter {it.name == "kotlinx-coroutines-debug-${coroutines_version}.jar" }.singleFile
+    jvmArgs ('-javaagent:' + coroutinesDebugJar)
+    testClassesDirs = sourceSet.output.classesDirs
+    classpath = sourceSet.runtimeClasspath
+    systemProperties project.properties.subMap(["overwrite.probes"])
+}
+
+task debugDynamicAgentTest(type: Test) {
+    def sourceSet = sourceSets.debugDynamicAgentTest
     testClassesDirs = sourceSet.output.classesDirs
     classpath = sourceSet.runtimeClasspath
 }
 
 task coreAgentTest(type: Test) {
     def sourceSet = sourceSets.coreAgentTest
-    dependsOn(project(':kotlinx-coroutines-core').jvmJar)
-    jvmArgs ('-javaagent:' + project(':kotlinx-coroutines-core').jvmJar.outputs.files.getFiles()[0])
+    def coroutinesCoreJar = sourceSet.runtimeClasspath.filter {it.name == "kotlinx-coroutines-core-jvm-${coroutines_version}.jar" }.singleFile
+    jvmArgs ('-javaagent:' + coroutinesCoreJar)
     testClassesDirs = sourceSet.output.classesDirs
     classpath = sourceSet.runtimeClasspath
 }
 
-dependencies {
-    testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
-    testImplementation 'junit:junit:4.12'
-    npmTestImplementation 'org.apache.commons:commons-compress:1.18'
-    npmTestImplementation 'com.google.code.gson:gson:2.8.5'
-    debugAgentTestCompile project(':kotlinx-coroutines-core')
-    debugAgentTestCompile project(':kotlinx-coroutines-debug')
-    coreAgentTestCompile project(':kotlinx-coroutines-core')
-}
-
 compileTestKotlin {
     kotlinOptions.jvmTarget = "1.8"
 }
 
 check {
-    dependsOn([npmTest, mavenTest, debugAgentTest, coreAgentTest])
+    dependsOn([withGuavaTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
 }
diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties
new file mode 100644
index 0000000..1038d81
--- /dev/null
+++ b/integration-testing/gradle.properties
@@ -0,0 +1,4 @@
+kotlin_version=1.6.21
+coroutines_version=1.6.4-SNAPSHOT
+
+kotlin.code.style=official
diff --git a/integration-testing/gradle/wrapper/gradle-wrapper.jar b/integration-testing/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7454180
--- /dev/null
+++ b/integration-testing/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/integration-testing/gradle/wrapper/gradle-wrapper.properties b/integration-testing/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..92f06b5
--- /dev/null
+++ b/integration-testing/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/integration-testing/gradlew b/integration-testing/gradlew
new file mode 100755
index 0000000..1b6c787
--- /dev/null
+++ b/integration-testing/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# 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  ;; #(
+  MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/integration-testing/gradlew.bat b/integration-testing/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/integration-testing/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+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 execute
+
+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
+
+: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 %*
+
+: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-testing/settings.gradle b/integration-testing/settings.gradle
new file mode 100644
index 0000000..67336c9
--- /dev/null
+++ b/integration-testing/settings.gradle
@@ -0,0 +1,19 @@
+pluginManagement {
+    resolutionStrategy {
+        eachPlugin {
+            if (requested.id.id == "org.jetbrains.kotlin.multiplatform" || requested.id.id == "org.jetbrains.kotlin.jvm") {
+                useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
+            }
+        }
+    }
+
+    repositories {
+        mavenCentral()
+        maven { url "https://plugins.gradle.org/m2/" }
+        mavenLocal()
+    }
+}
+
+include 'smokeTest'
+
+rootProject.name = "kotlinx-coroutines-integration-testing"
diff --git a/integration-testing/smokeTest/build.gradle b/integration-testing/smokeTest/build.gradle
new file mode 100644
index 0000000..b200bb2
--- /dev/null
+++ b/integration-testing/smokeTest/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+    id 'org.jetbrains.kotlin.multiplatform'
+}
+
+repositories {
+    // Coroutines from the outer project are published by previous CI buils step
+    mavenLocal()
+    mavenCentral()
+}
+
+kotlin {
+    jvm()
+    js(IR) {
+        nodejs()
+    }
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                implementation kotlin('stdlib-common')
+                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+            }
+        }
+        commonTest {
+            dependencies {
+                implementation kotlin('test-common')
+                implementation kotlin('test-annotations-common')
+                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
+            }
+        }
+        jsTest {
+            dependencies {
+                implementation kotlin('test-js')
+            }
+        }
+        jvmTest {
+            dependencies {
+                implementation kotlin('test')
+                implementation kotlin('test-junit')
+            }
+        }
+    }
+}
diff --git a/integration-testing/smokeTest/src/commonMain/kotlin/Sample.kt b/integration-testing/smokeTest/src/commonMain/kotlin/Sample.kt
new file mode 100644
index 0000000..c5da677
--- /dev/null
+++ b/integration-testing/smokeTest/src/commonMain/kotlin/Sample.kt
@@ -0,0 +1,9 @@
+import kotlinx.coroutines.*
+
+suspend fun doWorld() = coroutineScope {
+    launch {
+        delay(1000L)
+        println("World!")
+    }
+    println("Hello")
+}
diff --git a/integration-testing/smokeTest/src/commonTest/kotlin/SampleTest.kt b/integration-testing/smokeTest/src/commonTest/kotlin/SampleTest.kt
new file mode 100644
index 0000000..a8c6598
--- /dev/null
+++ b/integration-testing/smokeTest/src/commonTest/kotlin/SampleTest.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import kotlinx.coroutines.test.*
+import kotlin.test.*
+
+class SampleTest {
+    @Test
+    fun test() = runTest {
+        doWorld()
+    }
+}
diff --git a/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt b/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
index ce82e57..84886a1 100644
--- a/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
+++ b/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
@@ -20,20 +20,19 @@
         val classFileResourcePath = className.replace(".", "/") + ".class"
         val stream = clz.classLoader.getResourceAsStream(classFileResourcePath)!!
         val array = stream.readBytes()
-        val binFile = clz.classLoader.getResourceAsStream("DebugProbesKt.bin")!!
-        val binContent = binFile.readBytes()
+        // we expect the integration testing project to be in a subdirectory of the main kotlinx.coroutines project
+        val base = File("").absoluteFile.parentFile
+        val probes = File(base, "kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin")
+        val binContent = probes.readBytes()
         if (overwrite) {
-            val url = clz.classLoader.getResource("DebugProbesKt.bin")!!
-            val base = url.toExternalForm().toString().removePrefix("jar:file:").substringBefore("/build")
-            val probes = File(base, "jvm/resources/DebugProbesKt.bin")
             FileOutputStream(probes).use { it.write(array) }
             println("Content was successfully overwritten!")
         } else {
             assertTrue(
                 array.contentEquals(binContent),
                 "Compiled DebugProbesKt.class does not match the file shipped as a resource in kotlinx-coroutines-core. " +
-                        "Typically it happens because of the Kotlin version update (-> binary metadata). In that case, run the same test with -Poverwrite.probes=true and " +
-                        "ensure that classfile has major version equal to 50 (Java 6 compliance)")
+                        "Typically it happens because of the Kotlin version update (-> binary metadata). In that case, run the same test with -Poverwrite.probes=true."
+            )
         }
     }
 }
diff --git a/integration-testing/src/debugDynamicAgentTest/kotlin/DynamicAttachDebugTest.kt b/integration-testing/src/debugDynamicAgentTest/kotlin/DynamicAttachDebugTest.kt
new file mode 100644
index 0000000..ff9cac8
--- /dev/null
+++ b/integration-testing/src/debugDynamicAgentTest/kotlin/DynamicAttachDebugTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import org.junit.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.*
+import org.junit.Test
+import java.io.*
+import java.lang.IllegalStateException
+
+class DynamicAttachDebugTest {
+
+    @Test
+    fun testAgentDumpsCoroutines() =
+        DebugProbes.withDebugProbes {
+            runBlocking {
+                val baos = ByteArrayOutputStream()
+                DebugProbes.dumpCoroutines(PrintStream(baos))
+                // if the agent works, then dumps should contain something,
+                // at least the fact that this test is running.
+                Assert.assertTrue(baos.toString().contains("testAgentDumpsCoroutines"))
+            }
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun testAgentIsNotInstalled() {
+        DebugProbes.dumpCoroutines(PrintStream(ByteArrayOutputStream()))
+    }
+}
diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
similarity index 97%
rename from integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
rename to integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
index 39d6598..dbb1921 100644
--- a/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
+++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
@@ -8,7 +8,7 @@
 import org.junit.Assert.assertTrue
 import java.util.jar.*
 
-class MavenPublicationValidator {
+class MavenPublicationAtomicfuValidator {
     private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
 
     @Test
diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt
new file mode 100644
index 0000000..da87d4c
--- /dev/null
+++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016-2021 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.Test
+import java.util.jar.*
+import kotlin.test.*
+
+class MavenPublicationVersionValidator {
+
+    @Test
+    fun testMppJar() {
+        val clazz = Class.forName("kotlinx.coroutines.Job")
+        JarFile(clazz.protectionDomain.codeSource.location.file).checkForVersion("kotlinx_coroutines_core.version")
+    }
+
+    @Test
+    fun testAndroidJar() {
+        val clazz = Class.forName("kotlinx.coroutines.android.HandlerDispatcher")
+        JarFile(clazz.protectionDomain.codeSource.location.file).checkForVersion("kotlinx_coroutines_android.version")
+    }
+
+    private fun JarFile.checkForVersion(file: String) {
+        val actualFile = "META-INF/$file"
+        val version = System.getenv("version")
+        use {
+            for (e in entries()) {
+                if (e.name == actualFile) {
+                    val string = getInputStream(e).readAllBytes().decodeToString()
+                    assertEquals(version, string)
+                    return
+                }
+            }
+            error("File $file not found")
+        }
+    }
+}
diff --git a/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt b/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt
deleted file mode 100644
index 8e1b9f9..0000000
--- a/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2016-2021 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 java.io.*
-import java.util.zip.*
-import org.junit.Assert.*
-
-class NpmPublicationValidator {
-    private val VERSION = System.getenv("deployVersion")!!
-    private val BUILD_DIR = System.getenv("projectRoot")!!
-    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/integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt b/integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt
new file mode 100644
index 0000000..fefcc00
--- /dev/null
+++ b/integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import com.google.common.reflect.*
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class ListAllCoroutineThrowableSubclassesTest {
+
+    /*
+     * These are all the known throwables in kotlinx.coroutines.
+     * If you add one, this test will fail to make
+     * you ensure your exception type is java.io.Serializable.
+     *
+     * We do not have means to check it automatically, so checks are delegated to humans.
+     *
+     * See #3328 for serialization rationale.
+     */
+    private val knownThrowables = setOf(
+        "kotlinx.coroutines.TimeoutCancellationException",
+        "kotlinx.coroutines.JobCancellationException",
+        "kotlinx.coroutines.internal.UndeliveredElementException",
+        "kotlinx.coroutines.CompletionHandlerException",
+        "kotlinx.coroutines.DiagnosticCoroutineContextException",
+        "kotlinx.coroutines.CoroutinesInternalError",
+        "kotlinx.coroutines.channels.ClosedSendChannelException",
+        "kotlinx.coroutines.channels.ClosedReceiveChannelException",
+        "kotlinx.coroutines.flow.internal.ChildCancelledException",
+        "kotlinx.coroutines.flow.internal.AbortFlowException",
+        )
+
+    @Test
+    fun testThrowableSubclassesAreSerializable() {
+        val classes = ClassPath.from(this.javaClass.classLoader)
+            .getTopLevelClassesRecursive("kotlinx.coroutines");
+        val throwables = classes.filter { Throwable::class.java.isAssignableFrom(it.load()) }.map { it.toString() }
+        assertEquals(knownThrowables.sorted(), throwables.sorted())
+    }
+}
diff --git a/integration/kotlinx-coroutines-guava/README.md b/integration/kotlinx-coroutines-guava/README.md
index 34b8e58..c84fb86 100644
--- a/integration/kotlinx-coroutines-guava/README.md
+++ b/integration/kotlinx-coroutines-guava/README.md
@@ -51,17 +51,17 @@
 <!--- MODULE kotlinx-coroutines-core -->
 <!--- INDEX kotlinx.coroutines -->
 
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[CoroutineScope]: https://kotlinlang.org/api/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/future.html
-[com.google.common.util.concurrent.ListenableFuture.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/await.html
-[kotlinx.coroutines.Deferred.asListenableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/as-listenable-future.html
+[future]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/future.html
+[com.google.common.util.concurrent.ListenableFuture.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/await.html
+[kotlinx.coroutines.Deferred.asListenableFuture]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/as-listenable-future.html
 
 <!--- INDEX com.google.common.util.concurrent -->
 
-[com.google.common.util.concurrent.ListenableFuture]: https://kotlin.github.io/kotlinx.coroutines/https://google.github.io/guava/releases/28.0-jre/api/docs/com/google/common/util/concurrent/ListenableFuture.html
+[com.google.common.util.concurrent.ListenableFuture]: https://kotlinlang.org/api/kotlinx.coroutines/https://google.github.io/guava/releases/31.0.1-jre/api/docs/com/google/common/util/concurrent/ListenableFuture.html
 
 <!--- END -->
diff --git a/integration/kotlinx-coroutines-guava/build.gradle.kts b/integration/kotlinx-coroutines-guava/build.gradle.kts
index 12a6ca7..2a84ca9 100644
--- a/integration/kotlinx-coroutines-guava/build.gradle.kts
+++ b/integration/kotlinx-coroutines-guava/build.gradle.kts
@@ -2,10 +2,15 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
-val guavaVersion = "28.0-jre"
+val guavaVersion = "31.0.1-jre"
 
 dependencies {
-    compile("com.google.guava:guava:$guavaVersion")
+    api("com.google.guava:guava:$guavaVersion")
+}
+
+java {
+    targetCompatibility = JavaVersion.VERSION_1_8
+    sourceCompatibility = JavaVersion.VERSION_1_8
 }
 
 externalDocumentationLink(
diff --git a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
index 8f11e0a..0820f1f 100644
--- a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
+++ b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
@@ -14,7 +14,7 @@
 /**
  * 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
+ * The coroutine is started immediately. Passing [CoroutineStart.LAZY] to [start] throws
  * [IllegalArgumentException], because Futures don't have a way to start lazily.
  *
  * When the created coroutine [isCompleted][Job.isCompleted], it will try to
@@ -35,10 +35,12 @@
  * 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.
- * In particular, any exception that happens in the coroutine after returned future is
- * successfully cancelled will be passed to the [CoroutineExceptionHandler] from the [context].
- * See [ListenableFutureCoroutine] for details.
+ * Note that the error and cancellation semantics of [future] are _different_ than [async]'s.
+ * In contrast to [Deferred], [Future] doesn't have an intermediate `Cancelling` state. If
+ * the returned `Future` is successfully cancelled, and `block` throws afterward, the thrown
+ * error is dropped, and getting the `Future`'s value will throw a `CancellationException` with
+ * no cause. This is to match the specification and behavior of
+ * `java.util.concurrent.FutureTask`.
  *
  * @param context added overlaying [CoroutineScope.coroutineContext] to form the new context.
  * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
@@ -133,10 +135,8 @@
     // 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?) {
-            // Here we work with flexible types, so we unchecked cast to trick the type system
-            @Suppress("UNCHECKED_CAST")
-            runCatching { deferred.complete(result as T) }
+        override fun onSuccess(result: T) {
+            runCatching { deferred.complete(result) }
                 .onFailure { handleCoroutineException(EmptyCoroutineContext, it) }
         }
 
@@ -241,8 +241,8 @@
 
     return suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
         addListener(
-          ToContinuation(this, cont),
-          MoreExecutors.directExecutor())
+            ToContinuation(this, cont),
+            MoreExecutors.directExecutor())
         cont.invokeOnCancellation {
             cancel(false)
         }
@@ -284,16 +284,13 @@
  * By documented contract, a [Future] has been cancelled if
  * and only if its `isCancelled()` method returns true.
  *
- * Any error that occurs after successfully cancelling a [ListenableFuture] will be passed
- * to the [CoroutineExceptionHandler] from the context. The contract of [Future] does not permit
- * it to return an error after it is successfully cancelled.
- *
- * By calling [asListenableFuture] on a [Deferred], any error that occurs after successfully
- * cancelling the [ListenableFuture] representation of the [Deferred] will _not_ be passed to
- * the [CoroutineExceptionHandler]. Cancelling a [Deferred] places that [Deferred] in the
- * cancelling/cancelled states defined by [Job], which _can_ show the error. It's assumed that
- * the [Deferred] pointing to the task will be used to observe any error outcome occurring after
- * cancellation.
+ * Any error that occurs after successfully cancelling a [ListenableFuture] is lost.
+ * The contract of [Future] does not permit it to return an error after it is successfully cancelled.
+ * On the other hand, we can't report an unhandled exception to [CoroutineExceptionHandler],
+ * otherwise [Future.cancel] can lead to an app crash which arguably is a contract violation.
+ * In contrast to [Future] which can't change its outcome after a successful cancellation,
+ * cancelling a [Deferred] places that [Deferred] in the cancelling/cancelled states defined by [Job],
+ * which _can_ show the error.
  *
  * 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
@@ -312,10 +309,14 @@
     }
 
     override fun onCancelled(cause: Throwable, handled: Boolean) {
-        if (!future.completeExceptionallyOrCancel(cause) && !handled) {
-            // prevents loss of exception that was not handled by parent & could not be set to JobListenableFuture
-            handleCoroutineException(context, cause)
-        }
+        // Note: if future was cancelled in a race with a cancellation of this
+        // coroutine, and the future was successfully cancelled first, the cause of coroutine
+        // cancellation is dropped in this promise. A Future can only be completed once.
+        //
+        // This is consistent with FutureTask behaviour. A race between a Future.cancel() and
+        // a FutureTask.setException() for the same Future will similarly drop the
+        // cause of a failure-after-cancellation.
+        future.completeExceptionallyOrCancel(cause)
     }
 }
 
@@ -348,7 +349,7 @@
      *
      * To preserve Coroutine's [CancellationException], this future points to either `T` or [Cancelled].
      */
-    private val auxFuture = SettableFuture.create<Any>()
+    private val auxFuture = SettableFuture.create<Any?>()
 
     /**
      * `true` if [auxFuture.get][ListenableFuture.get] throws [ExecutionException].
@@ -433,7 +434,7 @@
     }
 
     /** See [get()]. */
-    private fun getInternal(result: Any): T = if (result is Cancelled) {
+    private fun getInternal(result: Any?): T = if (result is Cancelled) {
         throw CancellationException().initCause(result.exception)
     } else {
         // We know that `auxFuture` can contain either `T` or `Cancelled`.
diff --git a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
index 69ba193..511b1b0 100644
--- a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
+++ b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
@@ -555,11 +555,7 @@
     }
 
     @Test
-    fun testUnhandledExceptionOnExternalCancellation() = runTest(
-        unhandled = listOf(
-            { it -> it is TestException } // exception is unhandled because there is no parent
-        )
-    ) {
+    fun testUnhandledExceptionOnExternalCancellation() = runTest {
         expect(1)
         // No parent here (NonCancellable), so nowhere to propagate exception
         val result = future(NonCancellable + Dispatchers.Unconfined) {
@@ -567,7 +563,7 @@
                 delay(Long.MAX_VALUE)
             } finally {
                 expect(2)
-                throw TestException() // this exception cannot be handled
+                throw TestException() // this exception cannot be handled and is set to be lost.
             }
         }
         result.cancel(true)
@@ -708,23 +704,6 @@
         assertEquals(testException, thrown.cause)
     }
 
-    @Test
-    fun stressTestJobListenableFutureIsCancelledDoesNotThrow() = runTest {
-        repeat(1000) {
-            val deferred = CompletableDeferred<String>()
-            val asListenableFuture = deferred.asListenableFuture()
-            // We heed two threads to test a race condition.
-            withContext(Dispatchers.Default) {
-                val cancellationJob = launch {
-                    asListenableFuture.cancel(false)
-                }
-                while (!cancellationJob.isCompleted) {
-                    asListenableFuture.isCancelled // Shouldn't throw.
-                }
-            }
-        }
-    }
-
     private inline fun <reified T: Throwable> ListenableFuture<*>.checkFutureException() {
         val e = assertFailsWith<ExecutionException> { get() }
         val cause = e.cause!!
@@ -775,4 +754,61 @@
             assertEquals(count, completed.get())
         }
     }
+
+    @Test
+    fun testFuturePropagatesExceptionToParentAfterCancellation() = runTest {
+        val throwLatch = CompletableDeferred<Boolean>()
+        val cancelLatch = CompletableDeferred<Boolean>()
+        val parent = Job()
+        val scope = CoroutineScope(parent)
+        val exception = TestException("propagated to parent")
+        val future = scope.future {
+            cancelLatch.complete(true)
+            withContext(NonCancellable) {
+                throwLatch.await()
+                throw exception
+            }
+        }
+        cancelLatch.await()
+        future.cancel(true)
+        throwLatch.complete(true)
+        parent.join()
+        assertTrue(parent.isCancelled)
+        assertEquals(exception, parent.getCancellationException().cause)
+    }
+
+    // Stress tests.
+
+    @Test
+    fun testFutureDoesNotReportToCoroutineExceptionHandler() = runTest {
+        repeat(1000) {
+            supervisorScope { // Don't propagate failures in children to parent and other children.
+                val innerFuture = SettableFuture.create<Unit>()
+                val outerFuture = async { innerFuture.await() }
+
+                withContext(Dispatchers.Default) {
+                    launch { innerFuture.setException(TestException("can be lost")) }
+                    launch { outerFuture.cancel() }
+                    // nothing should be reported to CoroutineExceptionHandler, otherwise `Future.cancel` contract violation.
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testJobListenableFutureIsCancelledDoesNotThrow() = runTest {
+        repeat(1000) {
+            val deferred = CompletableDeferred<String>()
+            val asListenableFuture = deferred.asListenableFuture()
+            // We heed two threads to test a race condition.
+            withContext(Dispatchers.Default) {
+                val cancellationJob = launch {
+                    asListenableFuture.cancel(false)
+                }
+                while (!cancellationJob.isCompleted) {
+                    asListenableFuture.isCancelled // Shouldn't throw.
+                }
+            }
+        }
+    }
 }
diff --git a/integration/kotlinx-coroutines-jdk8/README.md b/integration/kotlinx-coroutines-jdk8/README.md
index 35808c6..321e293 100644
--- a/integration/kotlinx-coroutines-jdk8/README.md
+++ b/integration/kotlinx-coroutines-jdk8/README.md
@@ -54,15 +54,15 @@
 <!--- 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
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[Deferred]: https://kotlinlang.org/api/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/future.html
-[java.util.concurrent.CompletionStage.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html
-[java.util.concurrent.CompletionStage.asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-deferred.html
-[kotlinx.coroutines.Deferred.asCompletableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-completable-future.html
+[future]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/future.html
+[java.util.concurrent.CompletionStage.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html
+[java.util.concurrent.CompletionStage.asDeferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-deferred.html
+[kotlinx.coroutines.Deferred.asCompletableFuture]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-completable-future.html
 
 <!--- END -->
diff --git a/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt b/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
index 1d804e5..b0d72de 100644
--- a/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
+++ b/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
@@ -19,7 +19,6 @@
 private class StreamFlow<T>(private val stream: Stream<T>) : Flow<T> {
     private val consumed = atomic(false)
 
-    @InternalCoroutinesApi
     override suspend fun collect(collector: FlowCollector<T>) {
         if (!consumed.compareAndSet(false, true)) error("Stream.consumeAsFlow can be collected only once")
         try {
diff --git a/integration/kotlinx-coroutines-play-services/README.md b/integration/kotlinx-coroutines-play-services/README.md
index e5e0e61..17b6500 100644
--- a/integration/kotlinx-coroutines-play-services/README.md
+++ b/integration/kotlinx-coroutines-play-services/README.md
@@ -34,6 +34,12 @@
 val currentLocation = currentLocationTask.await(cancellationTokenSource) // cancelling `await` also cancels `currentLocationTask`, and vice versa
 ```
 
-[asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/as-deferred.html
-[await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/await.html
-[asTask]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/kotlinx.coroutines.-deferred/as-task.html
+
+<!--- MODULE kotlinx-coroutines-play-services -->
+<!--- INDEX kotlinx.coroutines.tasks -->
+
+[asDeferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/as-deferred.html
+[await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/await.html
+[asTask]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/as-task.html
+
+<!--- END -->
diff --git a/integration/kotlinx-coroutines-play-services/build.gradle.kts b/integration/kotlinx-coroutines-play-services/build.gradle.kts
index 59f3b0b..9f8a128 100644
--- a/integration/kotlinx-coroutines-play-services/build.gradle.kts
+++ b/integration/kotlinx-coroutines-play-services/build.gradle.kts
@@ -4,36 +4,17 @@
 
 val tasksVersion = "16.0.1"
 
-val artifactType = Attribute.of("artifactType", String::class.java)
-val unpackedAar = Attribute.of("unpackedAar", Boolean::class.javaObjectType)
-
-configurations.configureEach {
-    afterEvaluate {
-        if (isCanBeResolved) {
-            attributes.attribute(unpackedAar, true) // request all AARs to be unpacked
-        }
-    }
-}
+project.configureAar()
 
 dependencies {
-    attributesSchema {
-        attribute(unpackedAar)
-    }
-
-    artifactTypes {
-        create("aar") {
-            attributes.attribute(unpackedAar, false)
-        }
-    }
-
-    registerTransform(UnpackAar::class.java) {
-        from.attribute(unpackedAar, false).attribute(artifactType, "aar")
-        to.attribute(unpackedAar, true).attribute(artifactType, "jar")
-    }
-
+    configureAarUnpacking()
     api("com.google.android.gms:play-services-tasks:$tasksVersion") {
         exclude(group="com.android.support")
     }
+
+    // Required by robolectric
+    testImplementation("androidx.test:core:1.2.0")
+    testImplementation("androidx.test:monitor:1.2.0")
 }
 
 externalDocumentationLink(
diff --git a/integration/kotlinx-coroutines-play-services/src/Tasks.kt b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
index c37ac7a..0451d7b 100644
--- a/integration/kotlinx-coroutines-play-services/src/Tasks.kt
+++ b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
@@ -8,6 +8,8 @@
 
 import com.google.android.gms.tasks.*
 import kotlinx.coroutines.*
+import java.lang.Runnable
+import java.util.concurrent.Executor
 import kotlin.coroutines.*
 
 /**
@@ -71,7 +73,8 @@
             deferred.completeExceptionally(e)
         }
     } else {
-        addOnCompleteListener {
+        // Run the callback directly to avoid unnecessarily scheduling on the main thread.
+        addOnCompleteListener(DirectExecutor) {
             val e = it.exception
             if (e == null) {
                 @Suppress("UNCHECKED_CAST")
@@ -114,7 +117,8 @@
  * leads to an unspecified behaviour.
  */
 @ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
-public suspend fun <T> Task<T>.await(cancellationTokenSource: CancellationTokenSource): T = awaitImpl(cancellationTokenSource)
+public suspend fun <T> Task<T>.await(cancellationTokenSource: CancellationTokenSource): T =
+    awaitImpl(cancellationTokenSource)
 
 private suspend fun <T> Task<T>.awaitImpl(cancellationTokenSource: CancellationTokenSource?): T {
     // fast path
@@ -133,7 +137,8 @@
     }
 
     return suspendCancellableCoroutine { cont ->
-        addOnCompleteListener {
+        // Run the callback directly to avoid unnecessarily scheduling on the main thread.
+        addOnCompleteListener(DirectExecutor) {
             val e = it.exception
             if (e == null) {
                 @Suppress("UNCHECKED_CAST")
@@ -150,3 +155,12 @@
         }
     }
 }
+
+/**
+ * An [Executor] that just directly executes the [Runnable].
+ */
+private object DirectExecutor : Executor {
+    override fun execute(r: Runnable) {
+        r.run()
+    }
+}
diff --git a/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt b/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
index 6026ffd..e286ee1 100644
--- a/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
+++ b/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
@@ -2,10 +2,17 @@
 
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
+import java.util.concurrent.*
 
 class Handler(val looper: Looper) {
     fun post(r: Runnable): Boolean {
-        GlobalScope.launch { r.run() }
+        try {
+            GlobalScope.launch { r.run() }
+        } catch (e: RejectedExecutionException) {
+            // Execute leftover callbacks in place for tests
+            r.run()
+        }
+
         return true
     }
 }
diff --git a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
index b125192..34fbe23 100644
--- a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
+++ b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
@@ -45,8 +45,8 @@
     }
 
     @Test
-    fun testCancelledAsTask() {
-        val deferred = GlobalScope.async {
+    fun testCancelledAsTask() = runTest {
+        val deferred = async(Dispatchers.Default) {
             delay(100)
         }.apply { cancel() }
 
@@ -60,8 +60,8 @@
     }
 
     @Test
-    fun testThrowingAsTask() {
-        val deferred = GlobalScope.async<Int> {
+    fun testThrowingAsTask() = runTest({ e -> e is TestException }) {
+        val deferred = async<Int>(Dispatchers.Default) {
             throw TestException("Fail")
         }
 
diff --git a/integration/kotlinx-coroutines-slf4j/README.md b/integration/kotlinx-coroutines-slf4j/README.md
index e23d390..37f8700 100644
--- a/integration/kotlinx-coroutines-slf4j/README.md
+++ b/integration/kotlinx-coroutines-slf4j/README.md
@@ -21,6 +21,6 @@
 <!--- 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
+[MDCContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-slf4j/kotlinx.coroutines.slf4j/-m-d-c-context/index.html
 
 <!--- END -->
diff --git a/integration/kotlinx-coroutines-slf4j/build.gradle.kts b/integration/kotlinx-coroutines-slf4j/build.gradle.kts
index a341eef..3552333 100644
--- a/integration/kotlinx-coroutines-slf4j/build.gradle.kts
+++ b/integration/kotlinx-coroutines-slf4j/build.gradle.kts
@@ -3,10 +3,10 @@
  */
 
 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")
+    implementation("org.slf4j:slf4j-api:1.7.32")
+    testImplementation("io.github.microutils:kotlin-logging:2.1.0")
+    testRuntimeOnly("ch.qos.logback:logback-classic:1.2.7")
+    testRuntimeOnly("ch.qos.logback:logback-core:1.2.7")
 }
 
 externalDocumentationLink(
diff --git a/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
index 9528f2b..0fbfece 100644
--- a/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
+++ b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
@@ -28,7 +28,7 @@
  * }
  * ```
  *
- * Note that you cannot update MDC context from inside of the coroutine simply
+ * Note that you cannot update MDC context from inside 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.
@@ -43,6 +43,7 @@
     /**
      * The value of [MDC] context map.
      */
+    @Suppress("MemberVisibilityCanBePrivate")
     public val contextMap: MDCContextMap = MDC.getCopyOfContextMap()
 ) : ThreadContextElement<MDCContextMap>, AbstractCoroutineContextElement(Key) {
     /**
diff --git a/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
index 7d18359..532c47e 100644
--- a/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
+++ b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
@@ -102,9 +102,10 @@
         val mainDispatcher = kotlin.coroutines.coroutineContext[ContinuationInterceptor]!!
         withContext(Dispatchers.Default + MDCContext()) {
             assertEquals("myValue", MDC.get("myKey"))
+            assertEquals("myValue", coroutineContext[MDCContext]?.contextMap?.get("myKey"))
             withContext(mainDispatcher) {
                 assertEquals("myValue", MDC.get("myKey"))
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/js/example-frontend-js/README.md b/js/example-frontend-js/README.md
index ad61372..55e7088 100644
--- a/js/example-frontend-js/README.md
+++ b/js/example-frontend-js/README.md
@@ -15,4 +15,4 @@
 ```
 
 Built and deployed application is available at the library documentation site
-[here](https://kotlin.github.io/kotlinx.coroutines/example-frontend-js/index.html).
+[here](https://kotlinlang.org/api/kotlinx.coroutines/example-frontend-js/index.html).
diff --git a/js/example-frontend-js/src/ExampleMain.kt b/js/example-frontend-js/src/ExampleMain.kt
index d4e530b..67c6ef0 100644
--- a/js/example-frontend-js/src/ExampleMain.kt
+++ b/js/example-frontend-js/src/ExampleMain.kt
@@ -8,7 +8,7 @@
 import kotlinx.html.dom.*
 import kotlinx.html.js.onClickFunction
 import org.w3c.dom.*
-import kotlin.browser.*
+import kotlinx.browser.*
 import kotlin.coroutines.*
 import kotlin.math.*
 import kotlin.random.Random
diff --git a/kotlinx-coroutines-core/README.md b/kotlinx-coroutines-core/README.md
index c21e504..6f59b68 100644
--- a/kotlinx-coroutines-core/README.md
+++ b/kotlinx-coroutines-core/README.md
@@ -57,7 +57,6 @@
 | [SendChannel][kotlinx.coroutines.channels.SendChannel]       | [send][kotlinx.coroutines.channels.SendChannel.send]            | [onSend][kotlinx.coroutines.channels.SendChannel.onSend]          | [trySend][kotlinx.coroutines.channels.SendChannel.trySend]
 | [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive]   | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
 | [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveCatching][kotlinx.coroutines.channels.receiveCatching]  | [onReceiveCatching][kotlinx.coroutines.channels.onReceiveCatching] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
-| [Mutex][kotlinx.coroutines.sync.Mutex]                       | [lock][kotlinx.coroutines.sync.Mutex.lock]                      | [onLock][kotlinx.coroutines.sync.Mutex.onLock]                    | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
 | none                                                         | [delay][kotlinx.coroutines.delay]                               | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout]   | none
 
 # Package kotlinx.coroutines
@@ -84,66 +83,60 @@
 
 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 -->
 
-[kotlinx.coroutines.launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[kotlinx.coroutines.Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[kotlinx.coroutines.CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[kotlinx.coroutines.async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[kotlinx.coroutines.Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
-[kotlinx.coroutines.runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
-[kotlinx.coroutines.Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-[kotlinx.coroutines.Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
-[kotlinx.coroutines.NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
-[kotlinx.coroutines.CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
-[kotlinx.coroutines.delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[kotlinx.coroutines.yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
-[kotlinx.coroutines.withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[kotlinx.coroutines.withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
-[kotlinx.coroutines.withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
-[kotlinx.coroutines.awaitAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await-all.html
-[kotlinx.coroutines.joinAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/join-all.html
-[suspendCancellableCoroutine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html
-[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
-[kotlinx.coroutines.Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[kotlinx.coroutines.Job.onJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/on-join.html
-[kotlinx.coroutines.Job.isCompleted]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/is-completed.html
-[kotlinx.coroutines.Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
-[kotlinx.coroutines.Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
+[kotlinx.coroutines.launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[kotlinx.coroutines.Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[kotlinx.coroutines.CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[kotlinx.coroutines.async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[kotlinx.coroutines.Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[kotlinx.coroutines.runBlocking]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[CoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+[kotlinx.coroutines.Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[kotlinx.coroutines.Dispatchers.Unconfined]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
+[kotlinx.coroutines.NonCancellable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
+[kotlinx.coroutines.CoroutineExceptionHandler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
+[kotlinx.coroutines.delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[kotlinx.coroutines.yield]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
+[kotlinx.coroutines.withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[kotlinx.coroutines.withTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
+[kotlinx.coroutines.withTimeoutOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
+[kotlinx.coroutines.awaitAll]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await-all.html
+[kotlinx.coroutines.joinAll]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/join-all.html
+[suspendCancellableCoroutine]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html
+[NonCancellable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
+[kotlinx.coroutines.Job.join]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[kotlinx.coroutines.Job.onJoin]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/on-join.html
+[kotlinx.coroutines.Job.isCompleted]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/is-completed.html
+[kotlinx.coroutines.Deferred.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
+[kotlinx.coroutines.Deferred.onAwait]: https://kotlinlang.org/api/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
+[kotlinx.coroutines.sync.Mutex]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
+[kotlinx.coroutines.sync.Mutex.lock]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/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.trySend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
-[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
-[kotlinx.coroutines.channels.ReceiveChannel.tryReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/try-receive.html
-[kotlinx.coroutines.channels.receiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-catching.html
-[kotlinx.coroutines.channels.onReceiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
+[kotlinx.coroutines.channels.produce]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
+[kotlinx.coroutines.channels.ReceiveChannel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
+[kotlinx.coroutines.channels.ProducerScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
+[kotlinx.coroutines.channels.Channel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+[kotlinx.coroutines.channels.SendChannel.send]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
+[kotlinx.coroutines.channels.ReceiveChannel.receive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
+[kotlinx.coroutines.channels.SendChannel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html
+[kotlinx.coroutines.channels.SendChannel.onSend]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
+[kotlinx.coroutines.channels.SendChannel.trySend]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
+[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
+[kotlinx.coroutines.channels.ReceiveChannel.tryReceive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/try-receive.html
+[kotlinx.coroutines.channels.receiveCatching]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-catching.html
+[kotlinx.coroutines.channels.onReceiveCatching]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
 
 <!--- INDEX kotlinx.coroutines.selects -->
 
-[kotlinx.coroutines.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
-[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/on-timeout.html
+[kotlinx.coroutines.selects.select]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
+[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/on-timeout.html
 
 <!--- INDEX kotlinx.coroutines.test -->
 <!--- END -->
diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
index 50bfb60..dd7f889 100644
--- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
+++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
@@ -140,11 +140,24 @@
 	public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
 }
 
+public abstract interface class kotlinx/coroutines/CopyableThreadContextElement : kotlinx/coroutines/ThreadContextElement {
+	public abstract fun copyForChild ()Lkotlinx/coroutines/CopyableThreadContextElement;
+	public abstract fun mergeForChild (Lkotlin/coroutines/CoroutineContext$Element;)Lkotlin/coroutines/CoroutineContext;
+}
+
+public final class kotlinx/coroutines/CopyableThreadContextElement$DefaultImpls {
+	public static fun fold (Lkotlinx/coroutines/CopyableThreadContextElement;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+	public static fun get (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+	public static fun minusKey (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+	public static fun plus (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+}
+
 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 (Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
 	public static final fun newCoroutineContext (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
 }
 
@@ -156,6 +169,7 @@
 	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 limitedParallelism (I)Lkotlinx/coroutines/CoroutineDispatcher;
 	public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
 	public final fun plus (Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineDispatcher;
 	public final fun releaseInterceptedContinuation (Lkotlin/coroutines/Continuation;)V
@@ -273,12 +287,17 @@
 public abstract interface annotation class kotlinx/coroutines/DelicateCoroutinesApi : java/lang/annotation/Annotation {
 }
 
+public final class kotlinx/coroutines/DispatchedTaskKt {
+	public static final field MODE_CANCELLABLE I
+}
+
 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 fun shutdown ()V
 }
 
 public final class kotlinx/coroutines/DispatchersKt {
@@ -366,8 +385,13 @@
 public final class kotlinx/coroutines/Job$Key : kotlin/coroutines/CoroutineContext$Key {
 }
 
+public class kotlinx/coroutines/JobImpl : kotlinx/coroutines/JobSupport, kotlinx/coroutines/CompletableJob {
+	public fun <init> (Lkotlinx/coroutines/Job;)V
+	public fun complete ()Z
+	public fun completeExceptionally (Ljava/lang/Throwable;)Z
+}
+
 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;
@@ -447,6 +471,7 @@
 public abstract class kotlinx/coroutines/MainCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher {
 	public fun <init> ()V
 	public abstract fun getImmediate ()Lkotlinx/coroutines/MainCoroutineDispatcher;
+	public fun limitedParallelism (I)Lkotlinx/coroutines/CoroutineDispatcher;
 	public fun toString ()Ljava/lang/String;
 	protected final fun toStringInternalImpl ()Ljava/lang/String;
 }
@@ -543,6 +568,15 @@
 	public static final fun withTimeoutOrNull-KLykuaI (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 }
 
+public final class kotlinx/coroutines/YieldContext : kotlin/coroutines/AbstractCoroutineContextElement {
+	public static final field Key Lkotlinx/coroutines/YieldContext$Key;
+	public field dispatcherWasUnconfined Z
+	public fun <init> ()V
+}
+
+public final class kotlinx/coroutines/YieldContext$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
 public final class kotlinx/coroutines/YieldKt {
 	public static final fun yield (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 }
@@ -887,8 +921,6 @@
 	public static final fun asFlow ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
 	public static final fun asSharedFlow (Lkotlinx/coroutines/flow/MutableSharedFlow;)Lkotlinx/coroutines/flow/SharedFlow;
 	public static final fun asStateFlow (Lkotlinx/coroutines/flow/MutableStateFlow;)Lkotlinx/coroutines/flow/StateFlow;
-	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 synthetic fun buffer (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
 	public static final fun buffer (Lkotlinx/coroutines/flow/Flow;ILkotlinx/coroutines/channels/BufferOverflow;)Lkotlinx/coroutines/flow/Flow;
 	public static synthetic fun buffer$default (Lkotlinx/coroutines/flow/Flow;IILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
@@ -899,7 +931,7 @@
 	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 synthetic 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 fun combine (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
@@ -958,10 +990,6 @@
 	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
@@ -978,8 +1006,6 @@
 	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 onEmpty (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;
@@ -995,9 +1021,7 @@
 	public static final fun reduce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public static final fun replay (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
 	public static final fun replay (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
-	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 runningFold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
@@ -1061,6 +1085,7 @@
 }
 
 public abstract interface class kotlinx/coroutines/flow/SharedFlow : kotlinx/coroutines/flow/Flow {
+	public abstract fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public abstract fun getReplayCache ()Ljava/util/List;
 }
 
@@ -1284,36 +1309,3 @@
 	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/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle
index c45ca08..9791b44 100644
--- a/kotlinx-coroutines-core/build.gradle
+++ b/kotlinx-coroutines-core/build.gradle
@@ -14,6 +14,8 @@
 apply from: rootProject.file("gradle/compile-js-multiplatform.gradle")
 apply from: rootProject.file('gradle/publish-npm-js.gradle')
 
+apply from: rootProject.file('gradle/dokka.gradle.kts')
+apply from: rootProject.file('gradle/publish.gradle')
 /* ==========================================================================
   Configure source sets structure for kotlinx-coroutines-core:
 
@@ -70,39 +72,65 @@
  * 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.srcDirs = ["$platform/$srcDir"]
-        if (name == "jvmMain") {
-            resources.srcDirs = ["$platform/resources"]
-        } else if (name == "jvmTest") {
-            resources.srcDirs = ["$platform/test-resources"]
+    sourceSets.forEach {
+        SourceSetsKt.configureMultiplatform(it)
+    }
+
+    /*
+     * Configure four test runs:
+     * 1) Old memory model, Main thread
+     * 2) New memory model, Main thread
+     * 3) Old memory model, BG thread
+     * 4) New memory model, BG thread (required for Dispatchers.Main tests on Darwin)
+     *
+     * All new MM targets are build with optimize = true to have stress tests properly run.
+     */
+    targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests.class).configureEach {
+        binaries {
+            // Test for memory leaks using a special entry point that does not exit but returns from main
+            binaries.getTest("DEBUG").freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"]
         }
-        languageSettings {
-            progressiveMode = true
-            optInAnnotations.each { useExperimentalAnnotation(it) }
+
+        binaries.test("newMM", [DEBUG]) {
+            def thisTest = it
+            freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"]
+            optimized = true
+            binaryOptions["memoryModel"] = "experimental"
+            testRuns.create("newMM") {
+                setExecutionSourceFrom(thisTest)
+                // A hack to get different suffixes in the aggregated report.
+                executionTask.configure { targetName = "$targetName new MM" }
+            }
+        }
+
+        binaries.test("worker",  [DEBUG]) {
+            def thisTest = it
+            freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
+            testRuns.create("worker") {
+                setExecutionSourceFrom(thisTest)
+                executionTask.configure { targetName = "$targetName worker" }
+            }
+        }
+
+        binaries.test("workerWithNewMM", [DEBUG]) {
+            def thisTest = it
+            optimized = true
+            freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
+            binaryOptions["memoryModel"] = "experimental"
+            testRuns.create("workerWithNewMM") {
+                setExecutionSourceFrom(thisTest)
+                executionTask.configure { targetName = "$targetName worker with new MM" }
+            }
         }
     }
 
-    configure(targets) {
-        // Configure additional binaries and test runs -- one for each OS
-        if (["macos", "linux", "mingw"].any { name.startsWith(it) }) {
-            binaries {
-                // Test for memory leaks using a special entry point that does not exit but returns from main
-                binaries.getTest("DEBUG").freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"]
-                // Configure a separate test where code runs in background
-                test("background", [org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG]) {
-                    freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
-                }
-            }
-            testRuns {
-                background { setExecutionSourceFrom(binaries.backgroundDebugTest) }
-            }
-        }
+    jvm {
+        // For animal sniffer
+        withJava()
     }
 }
 
+
 configurations {
     configureKotlinJvmPlatform(kotlinCompilerPluginClasspath)
 }
@@ -159,28 +187,10 @@
     jvmTest.dependencies {
         api "org.jetbrains.kotlinx:lincheck:$lincheck_version"
         api "org.jetbrains.kotlinx:kotlinx-knit-test:$knit_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'
@@ -246,30 +256,37 @@
 
 static void configureJvmForLincheck(task) {
     task.minHeapSize = '1g'
-    task.maxHeapSize = '6g' // we may need more space for building an interleaving tree in the model checking mode
+    task.maxHeapSize = '4g' // we may need more space for building an interleaving tree in the model checking mode
     task.jvmArgs = ['--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED',   // required for transformation
-                     '--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode
+                    '--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode
     task.systemProperty 'kotlinx.coroutines.semaphore.segmentSize', '2'
     task.systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '1' // better for the model checking mode
 }
 
-task jdk16Test(type: Test, dependsOn: [compileTestKotlinJvm, checkJdk16]) {
-    classpath = files { jvmTest.classpath }
-    testClassesDirs = files { jvmTest.testClassesDirs }
-    executable = "$System.env.JDK_16/bin/java"
-    exclude '**/*LFStressTest.*' // lock-freedom tests use LockFreedomTestEnvironment which needs JDK8
-    exclude '**/*LincheckTest.*' // Lincheck tests use LinChecker which needs JDK8
-    exclude '**/exceptions/**'   // exceptions tests check suppressed exception which needs JDK8
-    exclude '**/ExceptionsGuideTest.*'
-    exclude '**/RunInterruptibleStressTest.*' // fails on JDK 1.6 due to JDK bug
+// Always check additional test sets
+task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest])
+check.dependsOn moreTest
+
+tasks.jvmLincheckTest {
+    kover {
+        enabled = false // Always disabled, lincheck doesn't really support coverage
+    }
 }
 
-// Run jdk16Test test only during nightly stress test
-jdk16Test.onlyIf { project.properties['stressTest'] != null }
+def commonKoverExcludes =
+        ["kotlinx.coroutines.debug.*", // Tested by debug module
+         "kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt.*", // Deprecated
+         "kotlinx.coroutines.scheduling.LimitingDispatcher", // Deprecated
+         "kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher" // Deprecated
+        ]
 
-// Always check additional test sets
-task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest, jdk16Test])
-check.dependsOn moreTest
+tasks.koverHtmlReport {
+    excludes = commonKoverExcludes
+}
+
+tasks.koverVerify {
+    excludes = commonKoverExcludes
+}
 
 task testsJar(type: Jar, dependsOn: jvmTestClasses) {
     classifier = 'tests'
diff --git a/kotlinx-coroutines-core/common/README.md b/kotlinx-coroutines-core/common/README.md
index fcfe334..041cd3e 100644
--- a/kotlinx-coroutines-core/common/README.md
+++ b/kotlinx-coroutines-core/common/README.md
@@ -60,17 +60,12 @@
 | [SendChannel][kotlinx.coroutines.channels.SendChannel]    | [send][kotlinx.coroutines.channels.SendChannel.send]                      | [onSend][kotlinx.coroutines.channels.SendChannel.onSend]                   | [trySend][kotlinx.coroutines.channels.SendChannel.trySend]
 | [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive]             | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive]             | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
 | [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveCatching][kotlinx.coroutines.channels.ReceiveChannel.receiveCatching] | [onReceiveCatching][kotlinx.coroutines.channels.ReceiveChannel.onReceiveCatching] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
-| [Mutex][kotlinx.coroutines.sync.Mutex]          | [lock][kotlinx.coroutines.sync.Mutex.lock]                            | [onLock][kotlinx.coroutines.sync.Mutex.onLock]                   | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
 | none            | [delay]                                        | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout]                   | none
 
 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.
@@ -98,67 +93,61 @@
 <!--- 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
-[asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
-[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
-[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
-[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
-[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
+[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[CoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[Dispatchers.Unconfined]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
+[newSingleThreadContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
+[newFixedThreadPoolContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-fixed-thread-pool-context.html
+[asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
+[NonCancellable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
+[CoroutineExceptionHandler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
+[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[yield]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
+[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[withTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
+[withTimeoutOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
+[awaitAll]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await-all.html
+[joinAll]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/join-all.html
+[suspendCancellableCoroutine]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html
+[Job.join]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[Job.onJoin]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/on-join.html
+[Job.isCompleted]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/is-completed.html
+[Deferred.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
+[Deferred.onAwait]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
+[newCoroutineContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-coroutine-context.html
+[DEBUG_PROPERTY_NAME]: https://kotlinlang.org/api/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
+[kotlinx.coroutines.sync.Mutex]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
+[kotlinx.coroutines.sync.Mutex.lock]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/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.trySend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
-[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
-[kotlinx.coroutines.channels.ReceiveChannel.tryReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/try-receive.html
-[kotlinx.coroutines.channels.ReceiveChannel.receiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-catching.html
-[kotlinx.coroutines.channels.ReceiveChannel.onReceiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
+[kotlinx.coroutines.channels.produce]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
+[kotlinx.coroutines.channels.ReceiveChannel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
+[kotlinx.coroutines.channels.ProducerScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
+[kotlinx.coroutines.channels.actor]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
+[kotlinx.coroutines.channels.SendChannel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html
+[kotlinx.coroutines.channels.ActorScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-actor-scope/index.html
+[kotlinx.coroutines.channels.Channel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+[kotlinx.coroutines.channels.SendChannel.send]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
+[kotlinx.coroutines.channels.ReceiveChannel.receive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
+[kotlinx.coroutines.channels.SendChannel.onSend]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
+[kotlinx.coroutines.channels.SendChannel.trySend]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
+[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
+[kotlinx.coroutines.channels.ReceiveChannel.tryReceive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/try-receive.html
+[kotlinx.coroutines.channels.ReceiveChannel.receiveCatching]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-catching.html
+[kotlinx.coroutines.channels.ReceiveChannel.onReceiveCatching]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
 
 <!--- INDEX kotlinx.coroutines.selects -->
 
-[kotlinx.coroutines.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
-[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/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
+[kotlinx.coroutines.selects.select]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
+[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/on-timeout.html
 
 <!--- END -->
diff --git a/kotlinx-coroutines-core/common/src/Annotations.kt b/kotlinx-coroutines-core/common/src/Annotations.kt
index 724cc8c..bacce39 100644
--- a/kotlinx-coroutines-core/common/src/Annotations.kt
+++ b/kotlinx-coroutines-core/common/src/Annotations.kt
@@ -30,6 +30,19 @@
  */
 @MustBeDocumented
 @Retention(value = AnnotationRetention.BINARY)
+@Target(
+    AnnotationTarget.CLASS,
+    AnnotationTarget.ANNOTATION_CLASS,
+    AnnotationTarget.PROPERTY,
+    AnnotationTarget.FIELD,
+    AnnotationTarget.LOCAL_VARIABLE,
+    AnnotationTarget.VALUE_PARAMETER,
+    AnnotationTarget.CONSTRUCTOR,
+    AnnotationTarget.FUNCTION,
+    AnnotationTarget.PROPERTY_GETTER,
+    AnnotationTarget.PROPERTY_SETTER,
+    AnnotationTarget.TYPEALIAS
+)
 @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
 public annotation class ExperimentalCoroutinesApi
 
diff --git a/kotlinx-coroutines-core/common/src/Await.kt b/kotlinx-coroutines-core/common/src/Await.kt
index e06ed33..c1669e2 100644
--- a/kotlinx-coroutines-core/common/src/Await.kt
+++ b/kotlinx-coroutines-core/common/src/Await.kt
@@ -29,8 +29,8 @@
  * 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 function is **not** equivalent to `this.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,
diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt
index a11ffe9..3dea68c 100644
--- a/kotlinx-coroutines-core/common/src/Builders.common.kt
+++ b/kotlinx-coroutines-core/common/src/Builders.common.kt
@@ -126,12 +126,15 @@
  * 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 with a **prompt cancellation guarantee**,
- * which means that if the original [coroutineContext], in which `withContext` was invoked,
- * is cancelled by the time its dispatcher starts to execute the code,
+ * Calls to [withContext] whose [context] argument provides a [CoroutineDispatcher] that is
+ * different from the current one, by necessity, perform additional dispatches: the [block]
+ * can not be executed immediately and needs to be dispatched for execution on
+ * the passed [CoroutineDispatcher], and then when the [block] completes, the execution
+ * has to shift back to the original dispatcher.
+ *
+ * Note that the result of `withContext` invocation is dispatched into the original context in a cancellable way
+ * with a **prompt cancellation guarantee**, which means that if the original [coroutineContext]
+ * in which `withContext` was invoked is cancelled by the time its dispatcher starts to execute the code,
  * it discards the result of `withContext` and throws [CancellationException].
  *
  * The cancellation behaviour described above is enabled if and only if the dispatcher is being changed.
@@ -148,7 +151,8 @@
     return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
         // compute new context
         val oldContext = uCont.context
-        val newContext = oldContext + context
+        // Copy CopyableThreadContextElement if necessary
+        val newContext = oldContext.newCoroutineContext(context)
         // always check for cancellation of new context
         newContext.ensureActive()
         // FAST PATH #1 -- new context is the same as the old one
diff --git a/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt
new file mode 100644
index 0000000..9c67032
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * [CoroutineDispatcher] that provides a method to close it,
+ * causing the rejection of any new tasks and cleanup of all underlying resources
+ * associated with the current dispatcher.
+ * Examples of closeable dispatchers are dispatchers backed by `java.lang.Executor` and
+ * by `kotlin.native.Worker`.
+ *
+ * **The `CloseableCoroutineDispatcher` class is not stable for inheritance in 3rd party libraries**, as new methods
+ * might be added to this interface in the future, but is stable for use.
+ */
+@ExperimentalCoroutinesApi
+public expect abstract class CloseableCoroutineDispatcher() : CoroutineDispatcher {
+
+    /**
+     * Initiate the closing sequence of the coroutine dispatcher.
+     * After a successful call to [close], no new tasks will
+     * be accepted to be [dispatched][dispatch], but the previously dispatched tasks will be run.
+     *
+     * Invocations of `close` are idempotent and thread-safe.
+     */
+    public abstract fun close()
+}
diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
index 68b4b1a..9153f39 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
@@ -7,13 +7,20 @@
 import kotlin.coroutines.*
 
 /**
- * Creates a context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
- * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on).
+ * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
+ * [ContinuationInterceptor] is specified and adds optional support for debugging facilities (when turned on)
+ * and copyable-thread-local facilities on JVM.
  */
 public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext
 
-internal expect fun createDefaultDispatcher(): CoroutineDispatcher
+/**
+ * Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext].
+ * @suppress
+ */
+@InternalCoroutinesApi
+public expect fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext
 
+@PublishedApi
 @Suppress("PropertyName")
 internal expect val DefaultDelay: Delay
 
diff --git a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
index d5613d4..568353b 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
@@ -45,6 +45,9 @@
      * potentially forming an event-loop to prevent stack overflows.
      * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation.
      *
+     * The [context] parameter represents the context of the coroutine that is being dispatched,
+     * or [EmptyCoroutineContext] if a non-coroutine-specific [Runnable] is dispatched instead.
+     *
      * A dispatcher can override this method to provide a performance optimization and avoid paying a cost of an unnecessary dispatch.
      * E.g. [MainCoroutineDispatcher.immediate] checks whether we are already in the required UI thread in this method and avoids
      * an additional dispatch when it is not required.
@@ -58,22 +61,78 @@
      *
      * This method should generally be exception-safe. An exception thrown from this method
      * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
+     *
+     * @see dispatch
+     * @see Dispatchers.Unconfined
      */
     public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
 
     /**
-     * Dispatches execution of a runnable [block] onto another thread in the given [context].
+     * Creates a view of the current dispatcher that limits the parallelism to the given [value][parallelism].
+     * The resulting view uses the original dispatcher for execution, but with the guarantee that
+     * no more than [parallelism] coroutines are executed at the same time.
+     *
+     * This method does not impose restrictions on the number of views or the total sum of parallelism values,
+     * each view controls its own parallelism independently with the guarantee that the effective parallelism
+     * of all views cannot exceed the actual parallelism of the original dispatcher.
+     *
+     * ### Limitations
+     *
+     * The default implementation of `limitedParallelism` does not support direct dispatchers,
+     * such as executing the given runnable in place during [dispatch] calls.
+     * Any dispatcher that may return `false` from [isDispatchNeeded] is considered direct.
+     * For direct dispatchers, it is recommended to override this method
+     * and provide a domain-specific implementation or to throw an [UnsupportedOperationException].
+     *
+     * ### Example of usage
+     * ```
+     * private val backgroundDispatcher = newFixedThreadPoolContext(4, "App Background")
+     * // At most 2 threads will be processing images as it is really slow and CPU-intensive
+     * private val imageProcessingDispatcher = backgroundDispatcher.limitedParallelism(2)
+     * // At most 3 threads will be processing JSON to avoid image processing starvation
+     * private val jsonProcessingDispatcher = backgroundDispatcher.limitedParallelism(3)
+     * // At most 1 thread will be doing IO
+     * private val fileWriterDispatcher = backgroundDispatcher.limitedParallelism(1)
+     * ```
+     * Note how in this example the application has an executor with 4 threads, but the total sum of all limits
+     * is 6. Still, at most 4 coroutines can be executed simultaneously as each view limits only its own parallelism.
+     *
+     * Note that this example was structured in such a way that it illustrates the parallelism guarantees.
+     * In practice, it is usually better to use [Dispatchers.IO] or [Dispatchers.Default] instead of creating a
+     * `backgroundDispatcher`. It is both possible and advised to call `limitedParallelism` on them.
+     */
+    @ExperimentalCoroutinesApi
+    public open fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+        parallelism.checkParallelism()
+        return LimitedDispatcher(this, parallelism)
+    }
+
+    /**
+     * Requests execution of a runnable [block].
+     * The dispatcher guarantees that [block] will eventually execute, typically by dispatching it to a thread pool,
+     * using a dedicated thread, or just executing the block in place.
+     * The [context] parameter represents the context of the coroutine that is being dispatched,
+     * or [EmptyCoroutineContext] if a non-coroutine-specific [Runnable] is dispatched instead.
+     * Implementations may use [context] for additional context-specific information,
+     * such as priority, whether the dispatched coroutine can be invoked in place,
+     * coroutine name, and additional diagnostic elements.
+     *
      * This method should guarantee that the given [block] will be eventually invoked,
      * otherwise the system may reach a deadlock state and never leave it.
-     * Cancellation mechanism is transparent for [CoroutineDispatcher] and is managed by [block] internals.
+     * The cancellation mechanism is transparent for [CoroutineDispatcher] and is managed by [block] internals.
      *
      * This method should generally be exception-safe. An exception thrown from this method
-     * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
+     * may leave the coroutines that use this dispatcher in an inconsistent and hard-to-debug state.
      *
-     * This method must not immediately call [block]. Doing so would result in [StackOverflowError]
-     * when [yield] is repeatedly called from a loop. However, an implementation that returns `false` from
-     * [isDispatchNeeded] can delegate this function to `dispatch` method of [Dispatchers.Unconfined], which is
-     * integrated with [yield] to avoid this problem.
+     * This method must not immediately call [block]. Doing so may result in `StackOverflowError`
+     * when `dispatch` is invoked repeatedly, for example when [yield] is called in a loop.
+     * In order to execute a block in place, it is required to return `false` from [isDispatchNeeded]
+     * and delegate the `dispatch` implementation to `Dispatchers.Unconfined.dispatch` in such cases.
+     * To support this, the coroutines machinery ensures in-place execution and forms an event-loop to
+     * avoid unbound recursion.
+     *
+     * @see isDispatchNeeded
+     * @see Dispatchers.Unconfined
      */
     public abstract fun dispatch(context: CoroutineContext, block: Runnable)
 
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
index 3ed233b..b0928d5 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
@@ -12,7 +12,7 @@
 import kotlin.coroutines.intrinsics.*
 
 /**
- * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc)
+ * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc.)
  * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
  * to automatically propagate all its elements and cancellation.
  *
@@ -28,8 +28,8 @@
  * By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a
  * [job][Job] to enforce the discipline of **structured concurrency** with propagation of cancellation.
  *
- * Every coroutine builder (like [launch], [async], etc)
- * and every scoping function (like [coroutineScope], [withContext], etc) provides _its own_ scope
+ * Every coroutine builder (like [launch], [async], and others)
+ * and every scoping function (like [coroutineScope] and [withContext]) 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 structured concurrency. See [Job] documentation for more details.
@@ -42,14 +42,14 @@
  * ### Custom usage
  *
  * `CoroutineScope` should be declared as a property on entities with a well-defined lifecycle that are
- * responsible for launching children coroutines. The corresponding instance of `CoroutineScope` shall be created
- * with either `CoroutineScope()` or `MainScope()` functions. The difference between them is only in the
- * [CoroutineDispatcher]:
+ * responsible for launching child coroutines. The corresponding instance of `CoroutineScope` shall be created
+ * with either `CoroutineScope()` or `MainScope()`:
  *
- * * `CoroutineScope()` uses [Dispatchers.Default] for its coroutines.
- * * `MainScope()` uses [Dispatchers.Main] for its coroutines.
+ * * `CoroutineScope()` uses the [context][CoroutineContext] provided to it as a parameter for its coroutines 
+ *   and adds a [Job] if one is not provided as part of the context.
+ * * `MainScope()` uses [Dispatchers.Main] for its coroutines and has a [SupervisorJob].
  *
- * **The key part of custom usage of `CustomScope` is cancelling it at the end of the lifecycle.**
+ * **The key part of custom usage of `CoroutineScope` is cancelling it at the end of the lifecycle.**
  * The [CoroutineScope.cancel] extension function shall be used when the entity that was launching coroutines
  * is no longer needed. It cancels all the coroutines that might still be running on behalf of it.
  *
@@ -178,7 +178,7 @@
  * ```
  * // concurrently load configuration and data
  * suspend fun loadConfigurationAndData() {
- *     coroutinesScope {
+ *     coroutineScope {
  *         launch { loadConfiguration() }
  *         launch { loadData() }
  *     }
@@ -269,8 +269,8 @@
  * 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 of any child coroutine in this scope cancels all the other children,
- * just like inside [coroutineScope] block.
+ * This way, failure of any child coroutine in this scope or [cancellation][CoroutineScope.cancel] of the scope itself
+ * cancels all the scope's children, just like inside [coroutineScope] block.
  */
 @Suppress("FunctionName")
 public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt
index 4543c5d..301ed2d 100644
--- a/kotlinx-coroutines-core/common/src/Delay.kt
+++ b/kotlinx-coroutines-core/common/src/Delay.kt
@@ -19,15 +19,12 @@
  */
 @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].
-     * There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
-     * suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details.
-     */
+
+    /** @suppress **/
+    @Deprecated(
+        message = "Deprecated without replacement as an internal method never intended for public use",
+        level = DeprecationLevel.ERROR
+    ) // Error since 1.6.0
     public suspend fun delay(time: Long) {
         if (time <= 0) return // don't delay
         return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, it) }
@@ -54,8 +51,6 @@
      * 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.
      */
     public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
         DefaultDelay.invokeOnTimeout(timeMillis, block, context)
@@ -138,7 +133,6 @@
  *
  * Implementation note: how exactly time is tracked is an implementation detail of [CoroutineDispatcher] in the context.
  */
-@ExperimentalTime
 public suspend fun delay(duration: Duration): Unit = delay(duration.toDelayMillis())
 
 /** Returns [Delay] implementation of the given context */
@@ -148,6 +142,5 @@
  * Convert this duration to its millisecond value.
  * Positive durations are coerced at least `1`.
  */
-@ExperimentalTime
 internal fun Duration.toDelayMillis(): Long =
     if (this > Duration.ZERO) inWholeMilliseconds.coerceAtLeast(1) else 0
diff --git a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
index 8681b18..28e67a4 100644
--- a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
+++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
@@ -26,9 +26,9 @@
      *
      * 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:
+     * 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
+     * - On JVM it is 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:
@@ -48,7 +48,7 @@
      * stack overflows.
      *
      * ### Event loop
-     * Event loop semantics is a purely internal concept and have no guarantees on the order of execution
+     * Event loop semantics is a purely internal concept and has 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.
      *
@@ -63,11 +63,11 @@
      * }
      * 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.
+     * Can print both "1 2 3" and "1 3 2". This is an implementation detail that can be changed.
+     * However, it is guaranteed that "Done" will be printed only when both `withContext` calls are completed.
      *
      * 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
+     * but still want to execute it in the current call-frame until its first suspension, 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].
diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
index e6a57c9..12940c5 100644
--- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt
+++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
@@ -115,7 +115,12 @@
         }
     }
 
-    protected open fun shutdown() {}
+    final override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+        parallelism.checkParallelism()
+        return this
+    }
+
+    open fun shutdown() {}
 }
 
 @ThreadLocal
@@ -231,8 +236,13 @@
         if (timeNanos < MAX_DELAY_NS) {
             val now = nanoTime()
             DelayedResumeTask(now + timeNanos, continuation).also { task ->
-                continuation.disposeOnCancellation(task)
+                /*
+                 * Order is important here: first we schedule the heap and only then
+                 * publish it to continuation. Otherwise, `DelayedResumeTask` would
+                 * have to know how to be disposed of even when it wasn't scheduled yet.
+                 */
                 schedule(now, task)
+                continuation.disposeOnCancellation(task)
             }
         }
     }
@@ -271,7 +281,7 @@
         // then process one event from queue
         val task = dequeue()
         if (task != null) {
-            task.run()
+            platformAutoreleasePool { task.run() }
             return 0
         }
         return nextTime
@@ -279,7 +289,7 @@
 
     public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block)
 
-    public fun enqueue(task: Runnable) {
+    open fun enqueue(task: Runnable) {
         if (enqueueImpl(task)) {
             // todo: we should unpark only when this delayed task became first in the queue
             unpark()
@@ -405,6 +415,7 @@
          */
         @JvmField var nanoTime: Long
     ) : Runnable, Comparable<DelayedTask>, DisposableHandle, ThreadSafeHeapNode {
+        @Volatile
         private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK
 
         override var heap: ThreadSafeHeap<*>?
@@ -526,3 +537,13 @@
     public fun enqueue(task: Runnable)
 }
 
+/**
+ * Used by Darwin targets to wrap a [Runnable.run] call in an Objective-C Autorelease Pool. It is a no-op on JVM, JS and
+ * non-Darwin native targets.
+ *
+ * Coroutines on Darwin targets can call into the Objective-C world, where a callee may push a to-be-returned object to
+ * the Autorelease Pool, so as to avoid a premature ARC release before it reaches the caller. This means the pool must
+ * be eventually drained to avoid leaks. Since Kotlin Coroutines does not use [NSRunLoop], which provides automatic
+ * pool management, it must manage the pool creation and pool drainage manually.
+ */
+internal expect inline fun platformAutoreleasePool(crossinline block: () -> Unit)
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
index 9552153..31d90ee 100644
--- a/kotlinx-coroutines-core/common/src/Job.kt
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -387,7 +387,7 @@
 /**
  * A handle to an allocated object that can be disposed to make it eligible for garbage collection.
  */
-public interface DisposableHandle {
+public fun interface DisposableHandle {
     /**
      * Disposes the corresponding object, making it eligible for garbage collection.
      * Repeated invocation of this function has no effect.
@@ -395,18 +395,6 @@
     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): DisposableHandle =
-    object : DisposableHandle {
-        override fun dispose() {
-            block()
-        }
-    }
-
 // -------------------- Parent-child communication --------------------
 
 /**
diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt
index 0a3dd23..1b5975c 100644
--- a/kotlinx-coroutines-core/common/src/JobSupport.kt
+++ b/kotlinx-coroutines-core/common/src/JobSupport.kt
@@ -1312,6 +1312,7 @@
     override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}"
 }
 
+@PublishedApi // for a custom job in the test module
 internal open class JobImpl(parent: Job?) : JobSupport(true), CompletableJob {
     init { initParentJob(parent) }
     override val onCancelComplete get() = true
diff --git a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
index 602da6e..a7065cc 100644
--- a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
@@ -4,6 +4,8 @@
 
 package kotlinx.coroutines
 
+import kotlinx.coroutines.internal.*
+
 /**
  * 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].
@@ -51,6 +53,12 @@
      */
     override fun toString(): String = toStringInternalImpl() ?: "$classSimpleName@$hexAddress"
 
+    override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+        parallelism.checkParallelism()
+        // MainCoroutineDispatcher is single-threaded -- short-circuit any attempts to limit it
+        return this
+    }
+
     /**
      * Internal method for more specific [toString] implementations. It returns non-null
      * string if this dispatcher is set in the platform as the main one.
diff --git a/kotlinx-coroutines-core/common/src/Timeout.kt b/kotlinx-coroutines-core/common/src/Timeout.kt
index 264a2b9..6a6829d 100644
--- a/kotlinx-coroutines-core/common/src/Timeout.kt
+++ b/kotlinx-coroutines-core/common/src/Timeout.kt
@@ -64,7 +64,6 @@
  *
  * > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
  */
-@ExperimentalTime
 public suspend fun <T> withTimeout(timeout: Duration, block: suspend CoroutineScope.() -> T): T {
     contract {
         callsInPlace(block, InvocationKind.EXACTLY_ONCE)
@@ -131,7 +130,6 @@
  *
  * > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
  */
-@ExperimentalTime
 public suspend fun <T> withTimeoutOrNull(timeout: Duration, block: suspend CoroutineScope.() -> T): T? =
         withTimeoutOrNull(timeout.toDelayMillis(), block)
 
@@ -165,7 +163,7 @@
  */
 public class TimeoutCancellationException internal constructor(
     message: String,
-    @JvmField internal val coroutine: Job?
+    @JvmField @Transient internal val coroutine: Job?
 ) : CancellationException(message), CopyableThrowable<TimeoutCancellationException> {
     /**
      * Creates a timeout exception with the given message.
@@ -175,7 +173,7 @@
     internal constructor(message: String) : this(message, null)
 
     // message is never null in fact
-    override fun createCopy(): TimeoutCancellationException? =
+    override fun createCopy(): TimeoutCancellationException =
         TimeoutCancellationException(message ?: "", coroutine).also { it.initCause(this) }
 }
 
diff --git a/kotlinx-coroutines-core/common/src/Unconfined.kt b/kotlinx-coroutines-core/common/src/Unconfined.kt
index 4f48645..5837ae8 100644
--- a/kotlinx-coroutines-core/common/src/Unconfined.kt
+++ b/kotlinx-coroutines-core/common/src/Unconfined.kt
@@ -11,10 +11,16 @@
  * A coroutine dispatcher that is not confined to any specific thread.
  */
 internal object Unconfined : CoroutineDispatcher() {
+
+    @ExperimentalCoroutinesApi
+    override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+        throw UnsupportedOperationException("limitedParallelism is not supported for Dispatchers.Unconfined")
+    }
+
     override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
 
     override fun dispatch(context: CoroutineContext, block: Runnable) {
-        // It can only be called by the "yield" function. See also code of "yield" function.
+        /** It can only be called by the [yield] function. See also code of [yield] function. */
         val yieldContext = context[YieldContext]
         if (yieldContext != null) {
             // report to "yield" that it is an unconfined dispatcher and don't call "block.run()"
@@ -32,6 +38,7 @@
 /**
  * Used to detect calls to [Unconfined.dispatch] from [yield] function.
  */
+@PublishedApi
 internal class YieldContext : AbstractCoroutineContextElement(Key) {
     companion object Key : CoroutineContext.Key<YieldContext>
 
diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
index 4751296..b92ced6 100644
--- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
@@ -136,7 +136,7 @@
         return sendSuspend(element)
     }
 
-    @Suppress("DEPRECATION")
+    @Suppress("DEPRECATION", "DEPRECATION_ERROR")
     override fun offer(element: E): Boolean {
         // Temporary migration for offer users who rely on onUndeliveredElement
         try {
diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
index 600eb6a..0a96f75 100644
--- a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
@@ -33,6 +33,11 @@
         require(capacity >= 1) { "ArrayBroadcastChannel capacity must be at least 1, but $capacity was specified" }
     }
 
+    /**
+     * NB: prior to changing any logic of ArrayBroadcastChannel internals, please ensure that
+     * you do not break internal invariants of the SubscriberList implementation on K/N and KJS
+     */
+
     /*
      *  Writes to buffer are guarded by bufferLock, but reads from buffer are concurrent with writes
      *    - Write element to buffer then write "tail" (volatile)
@@ -60,6 +65,7 @@
         get() = _size.value
         set(value) { _size.value = value }
 
+    @Suppress("DEPRECATION")
     private val subscribers = subscriberList<Subscriber<E>>()
 
     override val isBufferAlwaysFull: Boolean get() = false
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
index b15c426..5ad79fd 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -64,7 +64,6 @@
      */
     public val onSend: SelectClause2<E, SendChannel<E>>
 
-
     /**
      * Immediately adds the specified [element] to this channel, if this doesn't violate its capacity restrictions,
      * and returns the successful result. Otherwise, returns failed or closed result.
@@ -158,10 +157,10 @@
      * @suppress **Deprecated**.
      */
     @Deprecated(
-        level = DeprecationLevel.WARNING,
+        level = DeprecationLevel.ERROR,
         message = "Deprecated in the favour of 'trySend' method",
         replaceWith = ReplaceWith("trySend(element).isSuccess")
-    ) // Warning since 1.5.0
+    ) // Warning since 1.5.0, error since 1.6.0
     public fun offer(element: E): Boolean {
         val result = trySend(element)
         if (result.isSuccess) return true
@@ -314,12 +313,12 @@
      * @suppress **Deprecated**.
      */
     @Deprecated(
-        level = DeprecationLevel.WARNING,
+        level = DeprecationLevel.ERROR,
         message = "Deprecated in the favour of 'tryReceive'. " +
             "Please note that the provided replacement does not rethrow channel's close cause as 'poll' did, " +
             "for the precise replacement please refer to the 'poll' documentation",
         replaceWith = ReplaceWith("tryReceive().getOrNull()")
-    ) // Warning since 1.5.0
+    ) // Warning since 1.5.0, error since 1.6.0
     public fun poll(): E? {
         val result = tryReceive()
         if (result.isSuccess) return result.getOrThrow()
@@ -365,7 +364,7 @@
         message = "Deprecated in favor of onReceiveCatching extension",
         level = DeprecationLevel.ERROR,
         replaceWith = ReplaceWith("onReceiveCatching")
-    ) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.6.0
+    ) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.7.0
     public val onReceiveOrNull: SelectClause1<E?>
         get() {
             return object : SelectClause1<E?> {
@@ -685,7 +684,7 @@
  * exception which is either rethrown from the caller method or handed off to the exception handler in the current context
  * (see [CoroutineExceptionHandler]) when one is available.
  *
- * A typical usage for `onDeliveredElement` is to close a resource that is being transferred via the channel. The
+ * A typical usage for `onUndeliveredElement` is to close a resource that is being transferred via the channel. The
  * following code pattern guarantees that opened resources are closed even if producer, consumer, and/or channel
  * are cancelled. Resources are never lost.
  *
diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
index e0b4f9d..a78e2f1 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
@@ -50,7 +50,7 @@
 @Deprecated(
     "Deprecated in the favour of 'receiveCatching'",
     ReplaceWith("receiveCatching().getOrNull()"),
-    DeprecationLevel.WARNING
+    DeprecationLevel.ERROR
 ) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
 @Suppress("EXTENSION_SHADOWED_BY_MEMBER")
 public suspend fun <E : Any> ReceiveChannel<E>.receiveOrNull(): E? {
@@ -63,7 +63,7 @@
  */
 @Deprecated(
     "Deprecated in the favour of 'onReceiveCatching'",
-    level = DeprecationLevel.WARNING
+    level = DeprecationLevel.ERROR
 )  // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
 public fun <E : Any> ReceiveChannel<E>.onReceiveOrNull(): SelectClause1<E?> {
     @Suppress("DEPRECATION", "UNCHECKED_CAST")
diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
index f7f60cf..177e80c 100644
--- a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
@@ -19,7 +19,7 @@
  */
 internal open class ConflatedChannel<E>(onUndeliveredElement: OnUndeliveredElement<E>?) : AbstractChannel<E>(onUndeliveredElement) {
     protected final override val isBufferAlwaysEmpty: Boolean get() = false
-    protected final override val isBufferEmpty: Boolean get() = value === EMPTY
+    protected final override val isBufferEmpty: Boolean get() = lock.withLock { value === EMPTY }
     protected final override val isBufferAlwaysFull: Boolean get() = false
     protected final override val isBufferFull: Boolean get() = false
 
@@ -139,5 +139,5 @@
     // ------ debug ------
 
     override val bufferDebugString: String
-        get() = "(value=$value)"
+        get() = lock.withLock { "(value=$value)" }
 }
diff --git a/kotlinx-coroutines-core/common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt
index 3342fb6..da8f884 100644
--- a/kotlinx-coroutines-core/common/src/channels/Produce.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt
@@ -6,14 +6,11 @@
 
 import kotlinx.coroutines.*
 import kotlin.coroutines.*
+import kotlinx.coroutines.flow.*
 
 /**
- * Scope for the [produce][CoroutineScope.produce] coroutine builder.
- *
- * **Note: This is an experimental api.** Behavior of producers that work as children in a parent scope with respect
- *        to cancellation and error handling may change in the future.
+ * Scope for the [produce][CoroutineScope.produce], [callbackFlow] and [channelFlow] builders.
  */
-@ExperimentalCoroutinesApi
 public interface ProducerScope<in E> : CoroutineScope, SendChannel<E> {
     /**
      * A reference to the channel this coroutine [sends][send] elements to.
@@ -45,7 +42,6 @@
  * }
  * ```
  */
-@ExperimentalCoroutinesApi
 public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
     check(kotlin.coroutines.coroutineContext[Job] === this) { "awaitClose() can only be invoked from the producer context" }
     try {
@@ -137,7 +133,7 @@
     return coroutine
 }
 
-internal open class ProducerCoroutine<E>(
+private class ProducerCoroutine<E>(
     parentContext: CoroutineContext, channel: Channel<E>
 ) : ChannelCoroutine<E>(parentContext, channel, true, active = true), ProducerScope<E> {
     override val isActive: Boolean
diff --git a/kotlinx-coroutines-core/common/src/flow/Builders.kt b/kotlinx-coroutines-core/common/src/flow/Builders.kt
index 66b55a9..c4b55e1 100644
--- a/kotlinx-coroutines-core/common/src/flow/Builders.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Builders.kt
@@ -199,25 +199,6 @@
 }
 
 /**
- * @suppress
- */
-@FlowPreview
-@Deprecated(
-    message = "Use channelFlow with awaitClose { } instead of flowViaChannel and invokeOnClose { }.",
-    level = DeprecationLevel.ERROR
-) // To be removed in 1.4.x
-@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 a _cold_ [Flow] with elements that are sent to a [SendChannel]
  * provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
  * produced by code that is running in a different context or concurrently.
diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt
index 382953e..51ed427 100644
--- a/kotlinx-coroutines-core/common/src/flow/Channels.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt
@@ -179,45 +179,14 @@
 }
 
 /**
- * ### Deprecated
- *
- * **This API is deprecated.** The [BroadcastChannel] provides a complex channel-like API for hot flows.
- * [SharedFlow] is an easier-to-use and more flow-centric API for the same purposes, so using
- * [shareIn] operator is preferred. It is not a direct replacement, so please
- * study [shareIn] documentation to see what kind of shared flow fits your use-case. As a rule of thumb:
- *
- * * Replace `broadcastIn(scope)` and `broadcastIn(scope, CoroutineStart.LAZY)` with `shareIn(scope, 0, SharingStarted.Lazily)`.
- * * Replace `broadcastIn(scope, CoroutineStart.DEFAULT)` with `shareIn(scope, 0, SharingStarted.Eagerly)`.
- */
-@Deprecated(
-    message = "Use shareIn operator and the resulting SharedFlow as a replacement for BroadcastChannel",
-    replaceWith = ReplaceWith("this.shareIn(scope, SharingStarted.Lazily, 0)"),
-    level = DeprecationLevel.ERROR
-) // WARNING in 1.4.0, error in 1.5.0, removed in 1.6.0 (was @FlowPreview)
-public fun <T> Flow<T>.broadcastIn(
-    scope: CoroutineScope,
-    start: CoroutineStart = CoroutineStart.LAZY
-): BroadcastChannel<T> {
-    // Backwards compatibility with operator fusing
-    val channelFlow = asChannelFlow()
-    val capacity = when (channelFlow.onBufferOverflow) {
-        BufferOverflow.SUSPEND -> channelFlow.produceCapacity
-        BufferOverflow.DROP_OLDEST -> Channel.CONFLATED
-        BufferOverflow.DROP_LATEST ->
-            throw IllegalArgumentException("Broadcast channel does not support BufferOverflow.DROP_LATEST")
-    }
-    return scope.broadcast(channelFlow.context, capacity = capacity, start = start) {
-        collect { value ->
-            send(value)
-        }
-    }
-}
-
-/**
  * Creates a [produce] coroutine that collects the given flow.
  *
  * This transformation is **stateful**, it launches a [produce] coroutine
- * that collects the given flow and thus resulting channel should be properly closed or cancelled.
+ * that collects the given flow, and has the same behavior:
+ *
+ * * if collecting the flow throws, the channel will be closed with that exception
+ * * if the [ReceiveChannel] is cancelled, the collection of the flow will be cancelled
+ * * if collecting the flow completes normally, the [ReceiveChannel] will be closed normally
  *
  * A channel with [default][Channel.Factory.BUFFERED] buffer size is created.
  * Use [buffer] operator on the flow before calling `produceIn` to specify a value other than
diff --git a/kotlinx-coroutines-core/common/src/flow/Flow.kt b/kotlinx-coroutines-core/common/src/flow/Flow.kt
index 0ccd343..3520c48 100644
--- a/kotlinx-coroutines-core/common/src/flow/Flow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Flow.kt
@@ -108,7 +108,7 @@
  * val myFlow = flow {
  *    // GlobalScope.launch { // is prohibited
  *    // launch(Dispatchers.IO) { // is prohibited
- *    // withContext(CoroutineName("myFlow")) // is prohibited
+ *    // withContext(CoroutineName("myFlow")) { // is prohibited
  *    emit(1) // OK
  *    coroutineScope {
  *        emit(2) // OK -- still the same coroutine
@@ -131,10 +131,12 @@
  *
  * ### 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
+ * When `emit` or `emitAll` throws, the Flow implementations must immediately stop emitting new values and finish with an exception.
+ * For diagnostics or application-specific purposes, the exception may be different from the one thrown by the emit operation,
+ * suppressing the original exception as discussed below.
+ * If there is a need to emit values after the downstream failed, please use the [catch][Flow.catch] operator.
+ *
+ * The [catch][Flow.catch] operator only catches upstream exceptions, but passes
  * 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:
  *
@@ -147,6 +149,13 @@
  * ```
  * The same reasoning can be applied to the [onCompletion] operator that is a declarative replacement for the `finally` block.
  *
+ * All exception-handling Flow operators follow the principle of exception suppression:
+ *
+ * If the upstream flow throws an exception during its completion when the downstream exception has been thrown,
+ * the downstream exception becomes superseded and suppressed by the upstream exception, being a semantic
+ * equivalent of throwing from `finally` block. However, this doesn't affect the operation of the exception-handling operators,
+ * which consider the downstream exception to be the root cause and behave as if the upstream didn't throw anything.
+ *
  * 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.
@@ -163,19 +172,29 @@
  *
  * **The `Flow` interface is not stable for inheritance in 3rd party libraries**, as new methods
  * might be added to this interface in the future, but is stable for use.
- * Use the `flow { ... }` builder function to create an implementation.
+ *
+ * Use the `flow { ... }` builder function to create an implementation, or extend [AbstractFlow].
+ * These implementations ensure that the context preservation property is not violated, and prevent most
+ * of the developer mistakes related to concurrency, inconsistent flow dispatchers, and cancellation.
  */
 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.
+     * This method can be used along with SAM-conversion of [FlowCollector]:
+     * ```
+     * myFlow.collect { value -> println("Collected $value") }
+     * ```
+     *
+     * ### Method inheritance
+     *
+     * To ensure the context preservation property, it is not recommended implementing this method directly.
+     * Instead, [AbstractFlow] can be used as the base type to properly ensure flow's properties.
+     *
+     * All default flow implementations ensure context preservation and exception transparency properties on a best-effort basis
+     * and throw [IllegalStateException] if a violation was detected.
      */
-    @InternalCoroutinesApi
     public suspend fun collect(collector: FlowCollector<T>)
 }
 
@@ -205,7 +224,6 @@
 @FlowPreview
 public abstract class AbstractFlow<T> : Flow<T>, CancellableFlow<T> {
 
-    @InternalCoroutinesApi
     public final override suspend fun collect(collector: FlowCollector<T>) {
         val safeCollector = SafeCollector(collector, coroutineContext)
         try {
diff --git a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
index d1c1565..2877fe5 100644
--- a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
+++ b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
@@ -8,10 +8,25 @@
  * [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.
+ * This interface should usually not be implemented directly, but rather used as a receiver in a [flow] builder when implementing a custom operator,
+ * or with SAM-conversion.
  * Implementations of this interface are not thread-safe.
+ *
+ * Example of usage:
+ *
+ * ```
+ * 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 interface FlowCollector<in T> {
+public fun interface FlowCollector<in T> {
 
     /**
      * Collects the value emitted by the upstream.
diff --git a/kotlinx-coroutines-core/common/src/flow/Migration.kt b/kotlinx-coroutines-core/common/src/flow/Migration.kt
index 6278081..e398740 100644
--- a/kotlinx-coroutines-core/common/src/flow/Migration.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Migration.kt
@@ -260,7 +260,7 @@
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'forEach' is 'collect'",
-    replaceWith = ReplaceWith("collect(block)")
+    replaceWith = ReplaceWith("collect(action)")
 )
 public fun <T> Flow<T>.forEach(action: suspend (value: T) -> Unit): Unit = noImpl()
 
@@ -354,6 +354,7 @@
 )
 public fun <T> Flow<T>.concatWith(other: Flow<T>): Flow<T> = noImpl()
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'combineLatest' is 'combine'",
@@ -362,6 +363,7 @@
 public fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> =
     combine(this, other, transform)
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'combineLatest' is 'combine'",
@@ -373,6 +375,7 @@
     transform: suspend (T1, T2, T3) -> R
 ) = combine(this, other, other2, transform)
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'combineLatest' is 'combine'",
@@ -385,6 +388,7 @@
     transform: suspend (T1, T2, T3, T4) -> R
 ) = combine(this, other, other2, other3, transform)
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'combineLatest' is 'combine'",
@@ -422,6 +426,7 @@
 )
 public fun <T> Flow<T>.delayEach(timeMillis: Long): Flow<T> = onEach { delay(timeMillis) }
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogues of 'switchMap' are 'transformLatest', 'flatMapLatest' and 'mapLatest'",
@@ -429,6 +434,7 @@
 )
 public fun <T, R> Flow<T>.switchMap(transform: suspend (value: T) -> Flow<R>): Flow<R> = flatMapLatest(transform)
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR, // Warning since 1.3.8, was experimental when deprecated, ERROR since 1.5.0
     message = "'scanReduce' was renamed to 'runningReduce' to be consistent with Kotlin standard library",
@@ -436,6 +442,7 @@
 )
 public fun <T> Flow<T>.scanReduce(operation: suspend (accumulator: T, value: T) -> T): Flow<T> = runningReduce(operation)
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'publish()' is 'shareIn'. \n" +
@@ -446,6 +453,7 @@
 )
 public fun <T> Flow<T>.publish(): Flow<T> = noImpl()
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'publish(bufferSize)' is 'buffer' followed by 'shareIn'. \n" +
@@ -456,6 +464,7 @@
 )
 public fun <T> Flow<T>.publish(bufferSize: Int): Flow<T> = noImpl()
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'replay()' is 'shareIn' with unlimited replay. \n" +
@@ -466,6 +475,7 @@
 )
 public fun <T> Flow<T>.replay(): Flow<T> = noImpl()
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'replay(bufferSize)' is 'shareIn' with the specified replay parameter. \n" +
@@ -476,6 +486,7 @@
 )
 public fun <T> Flow<T>.replay(bufferSize: Int): Flow<T> = noImpl()
 
+/** @suppress */
 @Deprecated(
     level = DeprecationLevel.ERROR,
     message = "Flow analogue of 'cache()' is 'shareIn' with unlimited replay and 'started = SharingStared.Lazily' argument'",
diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
index d79e203..0a291f2 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
@@ -115,7 +115,7 @@
  * ### Implementation notes
  *
  * Shared flow implementation uses a lock to ensure thread-safety, but suspending collector and emitter coroutines are
- * resumed outside of this lock to avoid dead-locks when using unconfined coroutines. Adding new subscribers
+ * resumed outside of this lock to avoid deadlocks when using unconfined coroutines. Adding new subscribers
  * has `O(1)` amortized cost, but emitting has `O(N)` cost, where `N` is the number of subscribers.
  *
  * ### Not stable for inheritance
@@ -129,6 +129,18 @@
      * A snapshot of the replay cache.
      */
     public val replayCache: List<T>
+
+    /**
+     * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
+     * To emit values from a shared flow into a specific collector, either `collector.emitAll(flow)` or `collect { ... }`
+     * SAM-conversion can be used.
+     *
+     * **A shared flow never completes**. A call to [Flow.collect] or any other terminal operator
+     * on a shared flow never completes normally.
+     *
+     * @see [Flow.collect] for implementation and inheritance details.
+     */
+    override suspend fun collect(collector: FlowCollector<T>): Nothing
 }
 
 /**
@@ -155,8 +167,15 @@
  */
 public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
     /**
-     * Emits a [value] to this shared flow, suspending on buffer overflow if the shared flow was created
-     * with the default [BufferOverflow.SUSPEND] strategy.
+     * Emits a [value] to this shared flow, suspending on buffer overflow.
+     *
+     * This call can suspend only when the [BufferOverflow] strategy is
+     * [SUSPEND][BufferOverflow.SUSPEND] **and** there are subscribers collecting this shared flow.
+     *
+     * If there are no subscribers, the buffer is not used.
+     * Instead, the most recently emitted value is simply stored into
+     * the replay cache if one was configured, displacing the older elements there,
+     * or dropped if no replay cache was configured.
      *
      * See [tryEmit] for a non-suspending variant of this function.
      *
@@ -167,12 +186,16 @@
 
     /**
      * Tries to emit a [value] to this shared flow without suspending. It returns `true` if the value was
-     * emitted successfully. When this function returns `false`, it means that the call to a plain [emit]
-     * function will suspend until there is a buffer space available.
+     * emitted successfully (see below). When this function returns `false`, it means that a call to a plain [emit]
+     * function would suspend until there is buffer space available.
      *
-     * A shared flow configured with a [BufferOverflow] strategy other than [SUSPEND][BufferOverflow.SUSPEND]
-     * (either [DROP_OLDEST][BufferOverflow.DROP_OLDEST] or [DROP_LATEST][BufferOverflow.DROP_LATEST]) never
-     * suspends on [emit], and thus `tryEmit` to such a shared flow always returns `true`.
+     * This call can return `false` only when the [BufferOverflow] strategy is
+     * [SUSPEND][BufferOverflow.SUSPEND] **and** there are subscribers collecting this shared flow.
+     *
+     * If there are no subscribers, the buffer is not used.
+     * Instead, the most recently emitted value is simply stored into
+     * the replay cache if one was configured, displacing the older elements there,
+     * or dropped if no replay cache was configured. In any case, `tryEmit` returns `true`.
      *
      * This method is **thread-safe** and can be safely invoked from concurrent coroutines without
      * external synchronization.
@@ -198,6 +221,8 @@
      *     }
      *     .launchIn(scope) // launch it
      * ```
+     *
+     * Implementation note: the resulting flow **does not** conflate subscription count.
      */
     public val subscriptionCount: StateFlow<Int>
 
@@ -253,7 +278,7 @@
 
 // ------------------------------------ Implementation ------------------------------------
 
-private class SharedFlowSlot : AbstractSharedFlowSlot<SharedFlowImpl<*>>() {
+internal class SharedFlowSlot : AbstractSharedFlowSlot<SharedFlowImpl<*>>() {
     @JvmField
     var index = -1L // current "to-be-emitted" index, -1 means the slot is free now
 
@@ -275,7 +300,7 @@
     }
 }
 
-private class SharedFlowImpl<T>(
+internal open class SharedFlowImpl<T>(
     private val replay: Int,
     private val bufferCapacity: Int,
     private val onBufferOverflow: BufferOverflow
@@ -334,8 +359,15 @@
             result
         }
 
+    /*
+     * A tweak for SubscriptionCountStateFlow to get the latest value.
+     */
     @Suppress("UNCHECKED_CAST")
-    override suspend fun collect(collector: FlowCollector<T>) {
+    protected val lastReplayedLocked: T
+        get() = buffer!!.getBufferAt(replayIndex + replaySize - 1) as T
+
+    @Suppress("UNCHECKED_CAST")
+    override suspend fun collect(collector: FlowCollector<T>): Nothing {
         val slot = allocateSlot()
         try {
             if (collector is SubscribedFlowCollector) collector.onSubscription()
diff --git a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
index f4c6f2e..0554142 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
@@ -5,7 +5,7 @@
 package kotlinx.coroutines.flow
 
 import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.internal.IgnoreJreRequirement
 import kotlin.time.*
 
 /**
@@ -135,7 +135,6 @@
  * are negative.
  */
 @Suppress("FunctionName")
-@ExperimentalTime
 public fun SharingStarted.Companion.WhileSubscribed(
     stopTimeout: Duration = Duration.ZERO,
     replayExpiration: Duration = Duration.INFINITE
@@ -204,5 +203,6 @@
             stopTimeout == other.stopTimeout &&
             replayExpiration == other.replayExpiration
 
+    @IgnoreJreRequirement // desugared hashcode implementation
     override fun hashCode(): Int = stopTimeout.hashCode() * 31 + replayExpiration.hashCode()
 }
diff --git a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
index 9e82e78..be6cbd6 100644
--- a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
@@ -380,7 +380,7 @@
         throw UnsupportedOperationException("MutableStateFlow.resetReplayCache is not supported")
     }
 
-    override suspend fun collect(collector: FlowCollector<T>) {
+    override suspend fun collect(collector: FlowCollector<T>): Nothing {
         val slot = allocateSlot()
         try {
             if (collector is SubscribedFlowCollector) collector.onSubscription()
@@ -415,10 +415,6 @@
         fuseStateFlow(context, capacity, onBufferOverflow)
 }
 
-internal fun MutableStateFlow<Int>.increment(delta: Int) {
-    update { it + delta }
-}
-
 internal fun <T> StateFlow<T>.fuseStateFlow(
     context: CoroutineContext,
     capacity: Int,
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
index 7114cc0..39ca983 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
@@ -4,6 +4,7 @@
 
 package kotlinx.coroutines.flow.internal
 
+import kotlinx.coroutines.channels.*
 import kotlinx.coroutines.flow.*
 import kotlinx.coroutines.internal.*
 import kotlin.coroutines.*
@@ -26,12 +27,12 @@
     protected var nCollectors = 0 // number of allocated (!free) slots
         private set
     private var nextIndex = 0 // oracle for the next free slot index
-    private var _subscriptionCount: MutableStateFlow<Int>? = null // init on first need
+    private var _subscriptionCount: SubscriptionCountStateFlow? = null // init on first need
 
     val subscriptionCount: StateFlow<Int>
         get() = synchronized(this) {
             // allocate under lock in sync with nCollectors variable
-            _subscriptionCount ?: MutableStateFlow(nCollectors).also {
+            _subscriptionCount ?: SubscriptionCountStateFlow(nCollectors).also {
                 _subscriptionCount = it
             }
         }
@@ -43,7 +44,7 @@
     @Suppress("UNCHECKED_CAST")
     protected fun allocateSlot(): S {
         // Actually create slot under lock
-        var subscriptionCount: MutableStateFlow<Int>? = null
+        var subscriptionCount: SubscriptionCountStateFlow? = null
         val slot = synchronized(this) {
             val slots = when (val curSlots = slots) {
                 null -> createSlotArray(2).also { slots = it }
@@ -74,7 +75,7 @@
     @Suppress("UNCHECKED_CAST")
     protected fun freeSlot(slot: S) {
         // Release slot under lock
-        var subscriptionCount: MutableStateFlow<Int>? = null
+        var subscriptionCount: SubscriptionCountStateFlow? = null
         val resumes = synchronized(this) {
             nCollectors--
             subscriptionCount = _subscriptionCount // retrieve under lock if initialized
@@ -83,10 +84,10 @@
             (slot as AbstractSharedFlowSlot<Any>).freeLocked(this)
         }
         /*
-           Resume suspended coroutines.
-           This can happens when the subscriber that was freed was a slow one and was holding up buffer.
-           When this subscriber was freed, previously queued emitted can now wake up and are resumed here.
-        */
+         * Resume suspended coroutines.
+         * This can happen when the subscriber that was freed was a slow one and was holding up buffer.
+         * When this subscriber was freed, previously queued emitted can now wake up and are resumed here.
+         */
         for (cont in resumes) cont?.resume(Unit)
         // decrement subscription count
         subscriptionCount?.increment(-1)
@@ -99,3 +100,35 @@
         }
     }
 }
+
+/**
+ * [StateFlow] that represents the number of subscriptions.
+ *
+ * It is exposed as a regular [StateFlow] in our public API, but it is implemented as [SharedFlow] undercover to
+ * avoid conflations of consecutive updates because the subscription count is very sensitive to it.
+ *
+ * The importance of non-conflating can be demonstrated with the following example:
+ * ```
+ * val shared = flowOf(239).stateIn(this, SharingStarted.Lazily, 42) // stateIn for the sake of the initial value
+ * println(shared.first())
+ * yield()
+ * println(shared.first())
+ * ```
+ * If the flow is shared within the same dispatcher (e.g. Main) or with a slow/throttled one,
+ * the `SharingStarted.Lazily` will never be able to start the source: `first` sees the initial value and immediately
+ * unsubscribes, leaving the asynchronous `SharingStarted` with conflated zero.
+ *
+ * To avoid that (especially in a more complex scenarios), we do not conflate subscription updates.
+ */
+private class SubscriptionCountStateFlow(initialValue: Int) : StateFlow<Int>,
+    SharedFlowImpl<Int>(1, Int.MAX_VALUE, BufferOverflow.DROP_OLDEST)
+{
+    init { tryEmit(initialValue) }
+
+    override val value: Int
+        get() = synchronized(this) { lastReplayedLocked }
+
+    fun increment(delta: Int) = synchronized(this) {
+        tryEmit(lastReplayedLocked + delta)
+    }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
index b395525..9a81eef 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
@@ -51,33 +51,11 @@
         flowScope { block(this@flow) }
     }
 
-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.ATOMIC, 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 {
+    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/Merge.kt b/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
index 9eca8aa..c18adba 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
@@ -22,7 +22,7 @@
 
     override suspend fun flowCollect(collector: FlowCollector<R>) {
         assert { collector is SendingCollector } // So cancellation behaviour is not leaking into the downstream
-        flowScope {
+        coroutineScope {
             var previousFlow: Job? = null
             flow.collect { value ->
                 previousFlow?.apply {
@@ -49,7 +49,7 @@
         ChannelFlowMerge(flow, concurrency, context, capacity, onBufferOverflow)
 
     override fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> {
-        return scope.flowProduce(context, capacity, block = collectToFun)
+        return scope.produce(context, capacity, block = collectToFun)
     }
 
     override suspend fun collectTo(scope: ProducerScope<T>) {
@@ -87,7 +87,7 @@
         ChannelLimitedFlowMerge(flows, context, capacity, onBufferOverflow)
 
     override fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> {
-        return scope.flowProduce(context, capacity, block = collectToFun)
+        return scope.produce(context, capacity, block = collectToFun)
     }
 
     override suspend fun collectTo(scope: ProducerScope<T>) {
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
index 04342ed..8ed5606 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
@@ -171,7 +171,7 @@
  * ```
  *
  * Note that `conflate` operator is a shortcut for [buffer] with `capacity` of [Channel.CONFLATED][Channel.CONFLATED],
- * with is, in turn, a shortcut to a buffer that only keeps the latest element as
+ * which is, in turn, a shortcut to a buffer that only keeps the latest element as
  * created by `buffer(onBufferOverflow = `[`BufferOverflow.DROP_OLDEST`][BufferOverflow.DROP_OLDEST]`)`.
  *
  * ### Operator fusion
@@ -277,64 +277,6 @@
     }
 }
 
-/**
- * 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.
- *
- * @suppress
- */
-@FlowPreview
-@Deprecated(message = "flowWith is deprecated without replacement, please refer to its KDoc for an explanation", level = DeprecationLevel.ERROR) // Error in beta release, removal in 1.4
-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 = currentCoroutineContext().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
index fed5962..258dc3e 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
@@ -17,12 +17,12 @@
 /* Scaffolding for Knit code examples
 <!--- TEST_NAME FlowDelayTest -->
 <!--- PREFIX .*-duration-.*
-@file:OptIn(ExperimentalTime::class)
 ----- INCLUDE .*-duration-.*
 import kotlin.time.*
 ----- INCLUDE .*
 import kotlinx.coroutines.*
 import kotlinx.coroutines.flow.*
+import kotlin.time.Duration.Companion.milliseconds
 
 fun main() = runBlocking {
 ----- SUFFIX .*
@@ -149,7 +149,6 @@
  * Note that the resulting flow does not emit anything as long as the original flow emits
  * items faster than every [timeout] milliseconds.
  */
-@ExperimentalTime
 @FlowPreview
 public fun <T> Flow<T>.debounce(timeout: Duration): Flow<T> =
     debounce(timeout.toDelayMillis())
@@ -196,7 +195,6 @@
  *
  * @param timeout [T] is the emitted value and the return value is timeout in [Duration].
  */
-@ExperimentalTime
 @FlowPreview
 @JvmName("debounceDuration")
 @OptIn(kotlin.experimental.ExperimentalTypeInference::class)
@@ -345,6 +343,5 @@
  *
  * Note that the latest element is not emitted if it does not fit into the sampling window.
  */
-@ExperimentalTime
 @FlowPreview
 public fun <T> Flow<T>.sample(period: Duration): Flow<T> = sample(period.toDelayMillis())
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
index 0d67f88..f211a1b 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
@@ -15,7 +15,7 @@
 /**
  * Returns flow where all subsequent repetitions of the same value are filtered out.
  *
- * Note that any instance of [StateFlow] already behaves as if `distinctUtilChanged` operator is
+ * Note that any instance of [StateFlow] already behaves as if `distinctUntilChanged` operator is
  * applied to it, so applying `distinctUntilChanged` to a `StateFlow` has no effect.
  * See [StateFlow] documentation on Operator Fusion.
  * Also, repeated application of `distinctUntilChanged` operator on any flow has no effect.
@@ -71,7 +71,6 @@
     @JvmField val keySelector: (T) -> Any?,
     @JvmField val areEquivalent: (old: Any?, new: Any?) -> Boolean
 ): Flow<T> {
-    @InternalCoroutinesApi
     override suspend fun collect(collector: FlowCollector<T>) {
         var previousKey: Any? = NULL
         upstream.collect { value ->
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
index 608221e..30512f4 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
@@ -61,35 +61,6 @@
     }
 
 /**
- * @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.
@@ -124,16 +95,6 @@
     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,
@@ -186,6 +147,7 @@
     }
 
 // Return exception from upstream or null
+@Suppress("NAME_SHADOWING")
 internal suspend fun <T> Flow<T>.catchImpl(
     collector: FlowCollector<T>
 ): Throwable? {
@@ -200,6 +162,8 @@
             }
         }
     } catch (e: Throwable) {
+        // Otherwise, smartcast is impossible
+        val fromDownstream = fromDownstream
         /*
          * First check ensures that we catch an original exception, not one rethrown by an operator.
          * Seconds check ignores cancellation causes, they cannot be caught.
@@ -207,7 +171,41 @@
         if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {
             throw e // Rethrow exceptions from downstream and cancellation causes
         } else {
-            return e // not from downstream
+            /*
+             * The exception came from the upstream [semi-] independently.
+             * For pure failures, when the downstream functions normally, we handle the exception as intended.
+             * But if the downstream has failed prior to or concurrently
+             * with the upstream, we forcefully rethrow it, preserving the contextual information and ensuring  that it's not lost.
+             */
+            if (fromDownstream == null) {
+                return e
+            }
+            /*
+             * We consider the upstream exception as the superseding one when both upstream and downstream
+             * fail, suppressing the downstream exception, and operating similarly to `finally` block with
+             * the useful addition of adding the original downstream exception to suppressed ones.
+             *
+             * That's important for the following scenarios:
+             * ```
+             * flow {
+             *     val resource = ...
+             *     try {
+             *         ... emit as well ...
+             *     } finally {
+             *          resource.close() // Throws in the shutdown sequence when 'collect' already has thrown an exception
+             *     }
+             * }.catch { } // or retry
+             * .collect { ... }
+             * ```
+             * when *the downstream* throws.
+             */
+            if (e is CancellationException) {
+                fromDownstream.addSuppressed(e)
+                throw fromDownstream
+            } else {
+                e.addSuppressed(fromDownstream)
+                throw e
+            }
         }
     }
     return null
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
index 8fbf1a2..1b8abad 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
@@ -109,10 +109,8 @@
  *         emit(progress) // always emit progress
  *         !progress.isDone() // continue while download is not done
  *     }
- * }
  * ```
  */
-@ExperimentalCoroutinesApi
 public fun <T, R> Flow<T>.transformWhile(
     @BuilderInference transform: suspend FlowCollector<R>.(value: T) -> Boolean
 ): Flow<R> =
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
index 83f83e1..b140e62 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
@@ -13,6 +13,7 @@
 /**
  * Applying [cancellable][Flow.cancellable] to a [SharedFlow] has no effect.
  * See the [SharedFlow] documentation on Operator Fusion.
+ * @suppress
  */
 @Deprecated(
     level = DeprecationLevel.ERROR,
@@ -24,6 +25,7 @@
 /**
  * Applying [flowOn][Flow.flowOn] to [SharedFlow] has no effect.
  * See the [SharedFlow] documentation on Operator Fusion.
+ * @suppress
  */
 @Deprecated(
     level = DeprecationLevel.ERROR,
@@ -35,6 +37,7 @@
 /**
  * Applying [conflate][Flow.conflate] to [StateFlow] has no effect.
  * See the [StateFlow] documentation on Operator Fusion.
+ * @suppress
  */
 @Deprecated(
     level = DeprecationLevel.ERROR,
@@ -46,6 +49,7 @@
 /**
  * Applying [distinctUntilChanged][Flow.distinctUntilChanged] to [StateFlow] has no effect.
  * See the [StateFlow] documentation on Operator Fusion.
+ * @suppress
  */
 @Deprecated(
     level = DeprecationLevel.ERROR,
@@ -54,6 +58,9 @@
 )
 public fun <T> StateFlow<T>.distinctUntilChanged(): Flow<T> = noImpl()
 
+/**
+ * @suppress
+ */
 @Deprecated(
     message = "isActive is resolved into the extension of outer CoroutineScope which is likely to be an error." +
         "Use currentCoroutineContext().isActive or cancellable() operator instead " +
@@ -65,6 +72,9 @@
 public val FlowCollector<*>.isActive: Boolean
     get() = noImpl()
 
+/**
+ * @suppress
+ */
 @Deprecated(
     message = "cancel() is resolved into the extension of outer CoroutineScope which is likely to be an error." +
         "Use currentCoroutineContext().cancel() instead or specify the receiver of cancel() explicitly",
@@ -73,6 +83,9 @@
 )
 public fun FlowCollector<*>.cancel(cause: CancellationException? = null): Unit = noImpl()
 
+/**
+ * @suppress
+ */
 @Deprecated(
     message = "coroutineContext is resolved into the property of outer CoroutineScope which is likely to be an error." +
         "Use currentCoroutineContext() instead or specify the receiver of coroutineContext explicitly",
@@ -82,6 +95,9 @@
 public val FlowCollector<*>.coroutineContext: CoroutineContext
     get() = noImpl()
 
+/**
+ * @suppress
+ */
 @Deprecated(
     message = "SharedFlow never completes, so this operator typically has not effect, it can only " +
         "catch exceptions from 'onSubscribe' operator",
@@ -92,6 +108,9 @@
 public inline fun <T> SharedFlow<T>.catch(noinline action: suspend FlowCollector<T>.(cause: Throwable) -> Unit): Flow<T> =
     (this as Flow<T>).catch(action)
 
+/**
+ * @suppress
+ */
 @Deprecated(
     message = "SharedFlow never completes, so this operator has no effect.",
     level = DeprecationLevel.WARNING,
@@ -104,6 +123,9 @@
 ): Flow<T> =
     (this as Flow<T>).retry(retries, predicate)
 
+/**
+ * @suppress
+ */
 @Deprecated(
     message = "SharedFlow never completes, so this operator has no effect.",
     level = DeprecationLevel.WARNING,
@@ -113,6 +135,9 @@
 public inline fun <T> SharedFlow<T>.retryWhen(noinline predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T> =
     (this as Flow<T>).retryWhen(predicate)
 
+/**
+ * @suppress
+ */
 @Suppress("DeprecatedCallableAddReplaceWith")
 @Deprecated(
     message = "SharedFlow never completes, so this terminal operation never completes.",
@@ -122,6 +147,9 @@
 public suspend inline fun <T> SharedFlow<T>.toList(): List<T> =
     (this as Flow<T>).toList()
 
+/**
+ * @suppress
+ */
 @Suppress("DeprecatedCallableAddReplaceWith")
 @Deprecated(
     message = "SharedFlow never completes, so this terminal operation never completes.",
@@ -131,6 +159,9 @@
 public suspend inline fun <T> SharedFlow<T>.toSet(): Set<T> =
     (this as Flow<T>).toSet()
 
+/**
+ * @suppress
+ */
 @Suppress("DeprecatedCallableAddReplaceWith")
 @Deprecated(
     message = "SharedFlow never completes, so this terminal operation never completes.",
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
index 432160f..35c44d0 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
@@ -61,7 +61,7 @@
  * 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].
+ * at the same time. By default, it is equal to [DEFAULT_CONCURRENCY].
  */
 @FlowPreview
 public fun <T, R> Flow<T>.flatMapMerge(
@@ -71,8 +71,7 @@
     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.
+ * Flattens the given flow of flows into a single flow in a sequential manner, without interleaving nested flows.
  *
  * Inner flows are collected by this operator *sequentially*.
  */
@@ -90,7 +89,6 @@
  * Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
  * its concurrent merging so that only one properly configured channel is used for execution of merging logic.
  */
-@ExperimentalCoroutinesApi
 public fun <T> Iterable<Flow<T>>.merge(): Flow<T> {
     /*
      * This is a fuseable implementation of the following operator:
@@ -114,14 +112,13 @@
  * Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
  * its concurrent merging so that only one properly configured channel is used for execution of merging logic.
  */
-@ExperimentalCoroutinesApi
 public fun <T> merge(vararg flows: Flow<T>): Flow<T> = flows.asIterable().merge()
 
 /**
  * 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*.
+ * If [concurrency] is more than 1, then inner flows are collected by this operator *concurrently*.
  * With `concurrency == 1` this operator is identical to [flattenConcat].
  *
  * ### Operator fusion
@@ -133,7 +130,7 @@
  * and size of its output buffer can be changed by applying subsequent [buffer] operator.
  *
  * @param concurrency controls the number of in-flight flows, at most [concurrency] flows are collected
- * at the same time. By default it is equal to [DEFAULT_CONCURRENCY].
+ * 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> {
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
index 4fa74d8..2b690e3 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
@@ -197,8 +197,16 @@
     shared: MutableSharedFlow<T>,
     started: SharingStarted,
     initialValue: T
-): Job =
-    launch(context) { // the single coroutine to rule the sharing
+): Job {
+    /*
+     * Conditional start: in the case when sharing and subscribing happens in the same dispatcher, we want to
+     * have the following invariants preserved:
+     * * Delayed sharing strategies have a chance to immediately observe consecutive subscriptions.
+     *   E.g. in the cases like `flow.shareIn(...); flow.take(1)` we want sharing strategy to see the initial subscription
+     * * Eager sharing does not start immediately, so the subscribers have actual chance to subscribe _prior_ to sharing.
+     */
+    val start = if (started == SharingStarted.Eagerly) CoroutineStart.DEFAULT else CoroutineStart.UNDISPATCHED
+    return launch(context, start = start) { // the single coroutine to rule the sharing
         // Optimize common built-in started strategies
         when {
             started === SharingStarted.Eagerly -> {
@@ -230,6 +238,7 @@
             }
         }
     }
+}
 
 // -------------------------------- stateIn --------------------------------
 
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
index a47ae77..0f9e395 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
@@ -45,7 +45,7 @@
  * 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))
+    return@transform emit(transform(value))
 }
 
 /**
@@ -81,11 +81,10 @@
  * ```
  * flowOf(1, 2, 3).scan(emptyList<Int>()) { acc, value -> acc + value }.toList()
  * ```
- * will produce `[], [1], [1, 2], [1, 2, 3]]`.
+ * will produce `[], [1], [1, 2], [1, 2, 3]`.
  *
  * This function is an alias to [runningFold] operator.
  */
-@ExperimentalCoroutinesApi
 public fun <T, R> Flow<T>.scan(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow<R> = runningFold(initial, operation)
 
 /**
@@ -95,9 +94,8 @@
  * ```
  * flowOf(1, 2, 3).runningFold(emptyList<Int>()) { acc, value -> acc + value }.toList()
  * ```
- * will produce `[], [1], [1, 2], [1, 2, 3]]`.
+ * will produce `[], [1], [1, 2], [1, 2, 3]`.
  */
-@ExperimentalCoroutinesApi
 public fun <T, R> Flow<T>.runningFold(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow<R> = flow {
     var accumulator: R = initial
     emit(accumulator)
@@ -118,7 +116,6 @@
  * ```
  * will produce `[1, 3, 6, 10]`
  */
-@ExperimentalCoroutinesApi
 public fun <T> Flow<T>.runningReduce(operation: suspend (accumulator: T, value: T) -> T): Flow<T> = flow {
     var accumulator: Any? = NULL
     collect { value ->
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
index 771f833..98852c5 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
@@ -51,29 +51,6 @@
 }
 
 /**
- * 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.
  *
@@ -131,3 +108,10 @@
     ensureActive()
     flow.collect(this)
 }
+
+/** @suppress */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Backwards compatibility with JS and K/N")
+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)
+    })
diff --git a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
index 9f2699a..fb254a0 100644
--- a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
@@ -8,10 +8,12 @@
  * 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
+ * Note that this alias is intentionally not named as CopyOnWriteList to avoid accidental misusage outside of the ArrayBroadcastChannel
  */
 internal typealias SubscribersList<E> = MutableList<E>
 
+@Deprecated(message = "Implementation of this primitive is tailored to specific ArrayBroadcastChannel usages on K/N " +
+    "and K/JS platforms and it is unsafe to use it anywhere else")
 internal expect fun <E> subscriberList(): SubscribersList<E>
 
 internal expect class ReentrantLock() {
diff --git a/kotlinx-coroutines-core/common/src/internal/InternalAnnotations.common.kt b/kotlinx-coroutines-core/common/src/internal/InternalAnnotations.common.kt
new file mode 100644
index 0000000..1df81c9
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/InternalAnnotations.common.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+// Ignore JRE requirements for animal-sniffer, compileOnly dependency
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
+@OptionalExpectation
+internal expect annotation class IgnoreJreRequirement()
diff --git a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
new file mode 100644
index 0000000..28f37ec
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * The result of .limitedParallelism(x) call, a dispatcher
+ * that wraps the given dispatcher, but limits the parallelism level, while
+ * trying to emulate fairness.
+ */
+internal class LimitedDispatcher(
+    private val dispatcher: CoroutineDispatcher,
+    private val parallelism: Int
+) : CoroutineDispatcher(), Runnable, Delay by (dispatcher as? Delay ?: DefaultDelay) {
+
+    @Volatile
+    private var runningWorkers = 0
+
+    private val queue = LockFreeTaskQueue<Runnable>(singleConsumer = false)
+
+    // A separate object that we can synchronize on for K/N
+    private val workerAllocationLock = SynchronizedObject()
+
+    @ExperimentalCoroutinesApi
+    override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+        parallelism.checkParallelism()
+        if (parallelism >= this.parallelism) return this
+        return super.limitedParallelism(parallelism)
+    }
+
+    override fun run() {
+        var fairnessCounter = 0
+        while (true) {
+            val task = queue.removeFirstOrNull()
+            if (task != null) {
+                try {
+                    task.run()
+                } catch (e: Throwable) {
+                    handleCoroutineException(EmptyCoroutineContext, e)
+                }
+                // 16 is our out-of-thin-air constant to emulate fairness. Used in JS dispatchers as well
+                if (++fairnessCounter >= 16 && dispatcher.isDispatchNeeded(this)) {
+                    // Do "yield" to let other views to execute their runnable as well
+                    // Note that we do not decrement 'runningWorkers' as we still committed to do our part of work
+                    dispatcher.dispatch(this, this)
+                    return
+                }
+                continue
+            }
+
+            synchronized(workerAllocationLock) {
+                --runningWorkers
+                if (queue.size == 0) return
+                ++runningWorkers
+                fairnessCounter = 0
+            }
+        }
+    }
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        dispatchInternal(block) {
+            dispatcher.dispatch(this, this)
+        }
+    }
+
+    @InternalCoroutinesApi
+    override fun dispatchYield(context: CoroutineContext, block: Runnable) {
+        dispatchInternal(block) {
+            dispatcher.dispatchYield(this, this)
+        }
+    }
+
+    private inline fun dispatchInternal(block: Runnable, dispatch: () -> Unit) {
+        // Add task to queue so running workers will be able to see that
+        if (addAndTryDispatching(block)) return
+        /*
+         * Protect against the race when the number of workers is enough,
+         * but one (because of synchronized serialization) attempts to complete,
+         * and we just observed the number of running workers smaller than the actual
+         * number (hit right between `--runningWorkers` and `++runningWorkers` in `run()`)
+         */
+        if (!tryAllocateWorker()) return
+        dispatch()
+    }
+
+    private fun tryAllocateWorker(): Boolean {
+        synchronized(workerAllocationLock) {
+            if (runningWorkers >= parallelism) return false
+            ++runningWorkers
+            return true
+        }
+    }
+
+    private fun addAndTryDispatching(block: Runnable): Boolean {
+        queue.addLast(block)
+        return runningWorkers >= parallelism
+    }
+}
+
+// Save a few bytecode ops
+internal fun Int.checkParallelism() = require(this >= 1) { "Expected positive parallelism level, but got $this" }
diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
index 0e1d1b4..8b20ade 100644
--- a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
@@ -43,7 +43,7 @@
 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
+    public final override fun remove(): Nothing
 }
 
 /** @suppress **This is unstable API and it is subject to change.** */
diff --git a/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt
index 0b86386..45872f1 100644
--- a/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt
+++ b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt
@@ -14,6 +14,11 @@
     /**
      * Creates the main dispatcher. [allFactories] parameter contains all factories found by service loader.
      * This method is not guaranteed to be idempotent.
+     *
+     * It is required that this method fails with an exception instead of returning an instance that doesn't work
+     * correctly as a [Delay].
+     * The reason for this is that, on the JVM, [DefaultDelay] will use [Dispatchers.Main] for most delays by default
+     * if this method returns an instance without throwing.
      */
     public fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher
 
diff --git a/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
index 2d00768..2812b93 100644
--- a/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
@@ -40,6 +40,7 @@
  * The opposite of [recoverStackTrace].
  * It is guaranteed that `unwrap(recoverStackTrace(e)) === e`
  */
+@PublishedApi // published for the multiplatform implementation of kotlinx-coroutines-test
 internal expect fun <E: Throwable> unwrap(exception: E): E
 
 internal expect class StackTraceElement
diff --git a/kotlinx-coroutines-core/common/src/internal/Symbol.kt b/kotlinx-coroutines-core/common/src/internal/Symbol.kt
index 84db2ef..b629951 100644
--- a/kotlinx-coroutines-core/common/src/internal/Symbol.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Symbol.kt
@@ -4,12 +4,14 @@
 
 package kotlinx.coroutines.internal
 
+import kotlin.jvm.*
+
 /**
  * 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) {
+internal class Symbol(@JvmField val symbol: String) {
     override fun toString(): String = "<$symbol>"
 
     @Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt b/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt
index 43b7e9d..ad88103 100644
--- a/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt
+++ b/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt
@@ -37,6 +37,16 @@
         _size.value = 0
     }
 
+    public fun find(
+        predicate: (value: T) -> Boolean
+    ): T? = synchronized(this) block@{
+        for (i in 0 until size) {
+            val value = a?.get(i)!!
+            if (predicate(value)) return@block value
+        }
+        null
+    }
+
     public fun peek(): T? = synchronized(this) { firstImpl() }
 
     public fun removeFirstOrNull(): T? = synchronized(this) {
@@ -47,7 +57,6 @@
         }
     }
 
-    // @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)) {
@@ -59,7 +68,6 @@
 
     public fun addLast(node: T): Unit = 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())) {
diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt
index a717270..9213222 100644
--- a/kotlinx-coroutines-core/common/src/selects/Select.kt
+++ b/kotlinx-coroutines-core/common/src/selects/Select.kt
@@ -62,7 +62,6 @@
  * **Note: This is an experimental api.** It may be replaced with light-weight timer/timeout channels in the future.
  */
 @ExperimentalCoroutinesApi
-@ExperimentalTime
 public fun <R> SelectBuilder<R>.onTimeout(timeout: Duration, block: suspend () -> R): Unit =
         onTimeout(timeout.toDelayMillis(), block)
 
@@ -186,7 +185,6 @@
  * | [SendChannel]    | [send][SendChannel.send]                          | [onSend][SendChannel.onSend]
  * | [ReceiveChannel] | [receive][ReceiveChannel.receive]                 | [onReceive][ReceiveChannel.onReceive]
  * | [ReceiveChannel] | [receiveCatching][ReceiveChannel.receiveCatching] | [onReceiveCatching][ReceiveChannel.onReceiveCatching]
- * | [Mutex]          | [lock][Mutex.lock]                                | [onLock][Mutex.onLock]
  * | none             | [delay]                                           | [onTimeout][SelectBuilder.onTimeout]
  *
  * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
index 19584e0..681d5db 100644
--- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt
+++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
@@ -52,8 +52,7 @@
      * 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.
+     * Use [tryLock] to try acquiring a lock without waiting.
      *
      * This function is fair; suspended callers are resumed in first-in-first-out order.
      *
@@ -63,10 +62,10 @@
     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.
+     * Deprecated for removal without built-in replacement.
      */
+    @Deprecated(level = DeprecationLevel.WARNING, message = "Mutex.onLock deprecated without replacement. " +
+        "For additional details please refer to #2794") // WARNING since 1.6.0
     public val onLock: SelectClause2<Any?, Mutex>
 
     /**
@@ -362,7 +361,7 @@
     }
 
     private class LockedQueue(
-        @JvmField var owner: Any
+        @Volatile @JvmField var owner: Any
     ) : LockFreeLinkedListHead() {
         override fun toString(): String = "LockedQueue[$owner]"
     }
@@ -370,7 +369,7 @@
     private abstract inner class LockWaiter(
         @JvmField val owner: Any?
     ) : LockFreeLinkedListNode(), DisposableHandle {
-        private val isTaken = atomic<Boolean>(false)
+        private val isTaken = atomic(false)
         fun take(): Boolean = isTaken.compareAndSet(false, true)
         final override fun dispose() { remove() }
         abstract fun tryResumeLockWaiter(): Boolean
diff --git a/kotlinx-coroutines-core/common/test/DelayDurationTest.kt b/kotlinx-coroutines-core/common/test/DelayDurationTest.kt
index 3dd55bd..f054409 100644
--- a/kotlinx-coroutines-core/common/test/DelayDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/DelayDurationTest.kt
@@ -10,8 +10,9 @@
 
 import kotlin.test.*
 import kotlin.time.*
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.Duration.Companion.nanoseconds
 
-@ExperimentalTime
 class DelayDurationTest : TestBase() {
 
     @Test
diff --git a/kotlinx-coroutines-core/common/test/EmptyContext.kt b/kotlinx-coroutines-core/common/test/EmptyContext.kt
index ad78429..97efec3 100644
--- a/kotlinx-coroutines-core/common/test/EmptyContext.kt
+++ b/kotlinx-coroutines-core/common/test/EmptyContext.kt
@@ -7,10 +7,6 @@
 import kotlinx.coroutines.intrinsics.*
 import kotlin.coroutines.*
 
-suspend fun <T> withEmptyContext(block: suspend () -> T): T {
-    val baseline = Result.failure<T>(IllegalStateException("Block was suspended"))
-    var result: Result<T> = baseline
-    block.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) { result = it })
-    while (result == baseline) yield()
-    return result.getOrThrow()
+suspend fun <T> withEmptyContext(block: suspend () -> T): T = suspendCoroutine { cont ->
+    block.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) { cont.resumeWith(it) })
 }
diff --git a/kotlinx-coroutines-core/common/test/LimitedParallelismSharedTest.kt b/kotlinx-coroutines-core/common/test/LimitedParallelismSharedTest.kt
new file mode 100644
index 0000000..d01e857
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/LimitedParallelismSharedTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.test.*
+
+class LimitedParallelismSharedTest : TestBase() {
+
+    @Test
+    fun testLimitedDefault() = runTest {
+        // Test that evaluates the very basic completion of tasks in limited dispatcher
+        // for all supported platforms.
+        // For more specific and concurrent tests, see 'concurrent' package.
+        val view = Dispatchers.Default.limitedParallelism(1)
+        val view2 = Dispatchers.Default.limitedParallelism(1)
+        val j1 = launch(view) {
+            while (true) {
+                yield()
+            }
+        }
+        val j2 = launch(view2) { j1.cancel() }
+        joinAll(j1, j2)
+    }
+
+    @Test
+    fun testParallelismSpec() {
+        assertFailsWith<IllegalArgumentException> { Dispatchers.Default.limitedParallelism(0) }
+        assertFailsWith<IllegalArgumentException> { Dispatchers.Default.limitedParallelism(-1) }
+        assertFailsWith<IllegalArgumentException> { Dispatchers.Default.limitedParallelism(Int.MIN_VALUE) }
+        Dispatchers.Default.limitedParallelism(Int.MAX_VALUE)
+    }
+}
diff --git a/kotlinx-coroutines-core/common/test/TestBase.common.kt b/kotlinx-coroutines-core/common/test/TestBase.common.kt
index 71c4576..8b7024a 100644
--- a/kotlinx-coroutines-core/common/test/TestBase.common.kt
+++ b/kotlinx-coroutines-core/common/test/TestBase.common.kt
@@ -7,11 +7,13 @@
 package kotlinx.coroutines
 
 import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.internal.*
 import kotlin.coroutines.*
 import kotlin.test.*
 
 public expect val isStressTest: Boolean
 public expect val stressTestMultiplier: Int
+public expect val stressTestMultiplierSqrt: Int
 
 /**
  * The result of a multiplatform asynchronous test.
@@ -20,6 +22,8 @@
 @Suppress("NO_ACTUAL_FOR_EXPECT")
 public expect class TestResult
 
+public expect val isNative: Boolean
+
 public expect open class TestBase constructor() {
     /*
      * In common tests we emulate parameterized tests
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
index efd55fe..60e64f5 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
@@ -8,8 +8,9 @@
 
 import kotlin.test.*
 import kotlin.time.*
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 
-@ExperimentalTime
 class WithTimeoutDurationTest : TestBase() {
     /**
      * Tests a case of no timeout and no suspension inside.
@@ -17,7 +18,7 @@
     @Test
     fun testBasicNoSuspend() = runTest {
         expect(1)
-        val result = withTimeout(Duration.seconds(10)) {
+        val result = withTimeout(10.seconds) {
             expect(2)
             "OK"
         }
@@ -31,7 +32,7 @@
     @Test
     fun testBasicSuspend() = runTest {
         expect(1)
-        val result = withTimeout(Duration.seconds(10)) {
+        val result = withTimeout(10.seconds) {
             expect(2)
             yield()
             expect(3)
@@ -54,7 +55,7 @@
         }
         expect(2)
         // test that it does not yield to the above job when started
-        val result = withTimeout(Duration.seconds(1)) {
+        val result = withTimeout(1.seconds) {
             expect(3)
             yield() // yield only now
             expect(5)
@@ -74,7 +75,7 @@
     fun testYieldBlockingWithTimeout() = runTest(
             expected = { it is CancellationException }
     ) {
-        withTimeout(Duration.milliseconds(100)) {
+        withTimeout(100.milliseconds) {
             while (true) {
                 yield()
             }
@@ -87,7 +88,7 @@
     @Test
     fun testWithTimeoutChildWait() = runTest {
         expect(1)
-        withTimeout(Duration.milliseconds(100)) {
+        withTimeout(100.milliseconds) {
             expect(2)
             // launch child with timeout
             launch {
@@ -102,7 +103,7 @@
     @Test
     fun testBadClass() = runTest {
         val bad = BadClass()
-        val result = withTimeout(Duration.milliseconds(100)) {
+        val result = withTimeout(100.milliseconds) {
             bad
         }
         assertSame(bad, result)
@@ -118,9 +119,9 @@
     fun testExceptionOnTimeout() = runTest {
         expect(1)
         try {
-            withTimeout(Duration.milliseconds(100)) {
+            withTimeout(100.milliseconds) {
                 expect(2)
-                delay(Duration.milliseconds(1000))
+                delay(1000.milliseconds)
                 expectUnreached()
                 "OK"
             }
@@ -135,10 +136,10 @@
             expected = { it is CancellationException }
     ) {
         expect(1)
-        withTimeout(Duration.milliseconds(100)) {
+        withTimeout(100.milliseconds) {
             expect(2)
             try {
-                delay(Duration.milliseconds(1000))
+                delay(1000.milliseconds)
             } catch (e: CancellationException) {
                 finish(3)
             }
@@ -151,10 +152,10 @@
     fun testSuppressExceptionWithAnotherException() = runTest {
         expect(1)
         try {
-            withTimeout(Duration.milliseconds(100)) {
+            withTimeout(100.milliseconds) {
                 expect(2)
                 try {
-                    delay(Duration.milliseconds(1000))
+                    delay(1000.milliseconds)
                 } catch (e: CancellationException) {
                     expect(3)
                     throw TestException()
@@ -172,7 +173,7 @@
     fun testNegativeTimeout() = runTest {
         expect(1)
         try {
-            withTimeout(-Duration.milliseconds(1)) {
+            withTimeout(-1.milliseconds) {
                 expectUnreached()
                 "OK"
             }
@@ -187,7 +188,7 @@
         expect(1)
         try {
             expect(2)
-            withTimeout(Duration.seconds(1)) {
+            withTimeout(1.seconds) {
                 expect(3)
                 throw TestException()
             }
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
index b577775..92dba7b 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
@@ -10,8 +10,9 @@
 import kotlinx.coroutines.channels.*
 import kotlin.test.*
 import kotlin.time.*
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 
-@ExperimentalTime
 class WithTimeoutOrNullDurationTest : TestBase() {
     /**
      * Tests a case of no timeout and no suspension inside.
@@ -19,7 +20,7 @@
     @Test
     fun testBasicNoSuspend() = runTest {
         expect(1)
-        val result = withTimeoutOrNull(Duration.seconds(10)) {
+        val result = withTimeoutOrNull(10.seconds) {
             expect(2)
             "OK"
         }
@@ -33,7 +34,7 @@
     @Test
     fun testBasicSuspend() = runTest {
         expect(1)
-        val result = withTimeoutOrNull(Duration.seconds(10)) {
+        val result = withTimeoutOrNull(10.seconds) {
             expect(2)
             yield()
             expect(3)
@@ -56,7 +57,7 @@
         }
         expect(2)
         // test that it does not yield to the above job when started
-        val result = withTimeoutOrNull(Duration.seconds(1)) {
+        val result = withTimeoutOrNull(1.seconds) {
             expect(3)
             yield() // yield only now
             expect(5)
@@ -74,7 +75,7 @@
     @Test
     fun testYieldBlockingWithTimeout() = runTest {
         expect(1)
-        val result = withTimeoutOrNull(Duration.milliseconds(100)) {
+        val result = withTimeoutOrNull(100.milliseconds) {
             while (true) {
                 yield()
             }
@@ -86,7 +87,7 @@
     @Test
     fun testSmallTimeout() = runTest {
         val channel = Channel<Int>(1)
-        val value = withTimeoutOrNull(Duration.milliseconds(1)) {
+        val value = withTimeoutOrNull(1.milliseconds) {
             channel.receive()
         }
         assertNull(value)
@@ -103,8 +104,8 @@
     fun testInnerTimeout() = runTest(
         expected = { it is CancellationException }
     ) {
-        withTimeoutOrNull(Duration.milliseconds(1000)) {
-            withTimeout(Duration.milliseconds(10)) {
+        withTimeoutOrNull(1000.milliseconds) {
+            withTimeout(10.milliseconds) {
                 while (true) {
                     yield()
                 }
@@ -119,7 +120,7 @@
     fun testNestedTimeout() = runTest(expected = { it is TimeoutCancellationException }) {
         withTimeoutOrNull(Duration.INFINITE) {
             // Exception from this withTimeout is not suppressed by withTimeoutOrNull
-            withTimeout(Duration.milliseconds(10)) {
+            withTimeout(10.milliseconds) {
                 delay(Duration.INFINITE)
                 1
             }
@@ -131,9 +132,9 @@
     @Test
     fun testOuterTimeout() = runTest {
         var counter = 0
-        val result = withTimeoutOrNull(Duration.milliseconds(250)) {
+        val result = withTimeoutOrNull(320.milliseconds) {
             while (true) {
-                val inner = withTimeoutOrNull(Duration.milliseconds(100)) {
+                val inner = withTimeoutOrNull(150.milliseconds) {
                     while (true) {
                         yield()
                     }
@@ -149,7 +150,7 @@
     @Test
     fun testBadClass() = runTest {
         val bad = BadClass()
-        val result = withTimeoutOrNull(Duration.milliseconds(100)) {
+        val result = withTimeoutOrNull(100.milliseconds) {
             bad
         }
         assertSame(bad, result)
@@ -164,9 +165,9 @@
     @Test
     fun testNullOnTimeout() = runTest {
         expect(1)
-        val result = withTimeoutOrNull(Duration.milliseconds(100)) {
+        val result = withTimeoutOrNull(100.milliseconds) {
             expect(2)
-            delay(Duration.milliseconds(1000))
+            delay(1000.milliseconds)
             expectUnreached()
             "OK"
         }
@@ -177,10 +178,10 @@
     @Test
     fun testSuppressExceptionWithResult() = runTest {
         expect(1)
-        val result = withTimeoutOrNull(Duration.milliseconds(100)) {
+        val result = withTimeoutOrNull(100.milliseconds) {
             expect(2)
             try {
-                delay(Duration.milliseconds(1000))
+                delay(1000.milliseconds)
             } catch (e: CancellationException) {
                 expect(3)
             }
@@ -194,10 +195,10 @@
     fun testSuppressExceptionWithAnotherException() = runTest {
         expect(1)
         try {
-            withTimeoutOrNull(Duration.milliseconds(100)) {
+            withTimeoutOrNull(100.milliseconds) {
                 expect(2)
                 try {
-                    delay(Duration.milliseconds(1000))
+                    delay(1000.milliseconds)
                 } catch (e: CancellationException) {
                     expect(3)
                     throw TestException()
@@ -216,11 +217,11 @@
     @Test
     fun testNegativeTimeout() = runTest {
         expect(1)
-        var result = withTimeoutOrNull(-Duration.milliseconds(1)) {
+        var result = withTimeoutOrNull(-1.milliseconds) {
             expectUnreached()
         }
         assertNull(result)
-        result = withTimeoutOrNull(Duration.milliseconds(0)) {
+        result = withTimeoutOrNull(0.milliseconds) {
             expectUnreached()
         }
         assertNull(result)
@@ -232,7 +233,7 @@
         expect(1)
         try {
             expect(2)
-            withTimeoutOrNull<Unit>(Duration.milliseconds(1000)) {
+            withTimeoutOrNull<Unit>(1000.milliseconds) {
                 expect(3)
                 throw TestException()
             }
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
index 90bcf2d..ee896c9 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
@@ -129,9 +129,9 @@
     @Test
     fun testOuterTimeout() = runTest {
         var counter = 0
-        val result = withTimeoutOrNull(250) {
+        val result = withTimeoutOrNull(320) {
             while (true) {
-                val inner = withTimeoutOrNull(100) {
+                val inner = withTimeoutOrNull(150) {
                     while (true) {
                         yield()
                     }
diff --git a/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt b/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt
index b5f1bf7..5f39d32 100644
--- a/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt
@@ -235,7 +235,7 @@
             }
             expectUnreached()
         } catch (e: IllegalStateException) {
-            assertTrue(e.message!!.contains("Flow invariant is violated"))
+            assertTrue(e.message!!.contains("Flow invariant is violated"), "But had: ${e.message}")
             finish(2)
         }
     }
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
index 447eb73..ad91e49 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
@@ -144,4 +144,61 @@
             .collect()
         finish(9)
     }
+
+    @Test
+    fun testUpstreamExceptionConcurrentWithDownstream() = runTest {
+        val flow = flow {
+            try {
+                expect(1)
+                emit(1)
+            } finally {
+                expect(3)
+                throw TestException()
+            }
+        }.catch { expectUnreached() }.onEach {
+            expect(2)
+            throw TestException2()
+        }
+
+        assertFailsWith<TestException>(flow)
+        finish(4)
+    }
+
+    @Test
+    fun testUpstreamExceptionConcurrentWithDownstreamCancellation() = runTest {
+        val flow = flow {
+            try {
+                expect(1)
+                emit(1)
+            } finally {
+                expect(3)
+                throw TestException()
+            }
+        }.catch { expectUnreached() }.onEach {
+            expect(2)
+            throw CancellationException("")
+        }
+
+        assertFailsWith<TestException>(flow)
+        finish(4)
+    }
+
+    @Test
+    fun testUpstreamCancellationIsIgnoredWhenDownstreamFails() = runTest {
+        val flow = flow {
+            try {
+                expect(1)
+                emit(1)
+            } finally {
+                expect(3)
+                throw CancellationException("")
+            }
+        }.catch { expectUnreached() }.onEach {
+            expect(2)
+            throw TestException("")
+        }
+
+        assertFailsWith<TestException>(flow)
+        finish(4)
+    }
 }
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
index aa0893e..0268a23 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
@@ -7,7 +7,7 @@
 import kotlinx.coroutines.*
 import kotlinx.coroutines.channels.*
 import kotlin.test.*
-import kotlin.time.*
+import kotlin.time.Duration.Companion.milliseconds
 
 class DebounceTest : TestBase() {
     @Test
@@ -198,31 +198,29 @@
         finish(4)
     }
 
-    @ExperimentalTime
     @Test
     fun testDurationBasic() = withVirtualTime {
         expect(1)
         val flow = flow {
             expect(3)
             emit("A")
-            delay(Duration.milliseconds(1500))
+            delay(1500.milliseconds)
             emit("B")
-            delay(Duration.milliseconds(500))
+            delay(500.milliseconds)
             emit("C")
-            delay(Duration.milliseconds(250))
+            delay(250.milliseconds)
             emit("D")
-            delay(Duration.milliseconds(2000))
+            delay(2000.milliseconds)
             emit("E")
             expect(4)
         }
 
         expect(2)
-        val result = flow.debounce(Duration.milliseconds(1000)).toList()
+        val result = flow.debounce(1000.milliseconds).toList()
         assertEquals(listOf("A", "D", "E"), result)
         finish(5)
     }
 
-    @ExperimentalTime
     @Test
     fun testDebounceSelectorBasic() = withVirtualTime {
         expect(1)
@@ -271,7 +269,6 @@
         finish(5)
     }
 
-    @ExperimentalTime
     @Test
     fun testZeroDebounceTimeSelector() = withVirtualTime {
         expect(1)
@@ -289,20 +286,19 @@
         finish(5)
     }
 
-    @ExperimentalTime
     @Test
     fun testDebounceDurationSelectorBasic() = withVirtualTime {
         expect(1)
         val flow = flow {
             expect(3)
             emit("A")
-            delay(Duration.milliseconds(1500))
+            delay(1500.milliseconds)
             emit("B")
-            delay(Duration.milliseconds(500))
+            delay(500.milliseconds)
             emit("C")
-            delay(Duration.milliseconds(250))
+            delay(250.milliseconds)
             emit("D")
-            delay(Duration.milliseconds(2000))
+            delay(2000.milliseconds)
             emit("E")
             expect(4)
         }
@@ -310,9 +306,9 @@
         expect(2)
         val result = flow.debounce {
             if (it == "C") {
-                Duration.milliseconds(0)
+                0.milliseconds
             } else {
-                Duration.milliseconds(1000)
+                1000.milliseconds
             }
         }.toList()
 
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt
index 1c5a305..dfa2827 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt
@@ -48,10 +48,9 @@
                 expectUnreached()
             }
         }.drop(1)
-            .map {
+            .map<Int, Int> {
                 expect(4)
                 throw TestException()
-                42
             }.catch { emit(42) }
 
         expect(1)
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt
index 3de5d54..f52416d 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt
@@ -38,7 +38,6 @@
         }.filter {
             latch.receive()
             throw TestException()
-            true
         }.catch { emit(42) }
 
         assertEquals(42, flow.single())
@@ -74,7 +73,6 @@
         }.filterNot {
             latch.receive()
             throw TestException()
-            true
         }.catch { emit(42) }
 
         assertEquals(42, flow.single())
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
index 4095172..f09db12 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
@@ -72,7 +72,7 @@
             emit(2)
             expectUnreached()
         }.flatMap {
-            if (it == 1) flow<Int> {
+            if (it == 1) flow {
                 expect(5)
                 latch.send(Unit)
                 hang { expect(7) }
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt
index a92189c..f810221 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt
@@ -39,19 +39,14 @@
 
     @Test
     fun testCancellationExceptionDownstream() = runTest {
-        val flow = flow {
-            emit(1)
-            hang { expect(2) }
-        }.flatMapMerge {
+        val flow = flowOf(1, 2, 3).flatMapMerge {
             flow {
                 emit(it)
-                expect(1)
                 throw CancellationException("")
             }
         }.buffer(64)
 
-        assertFailsWith<CancellationException>(flow)
-        finish(3)
+        assertEquals(listOf(1, 2, 3), flow.toList())
     }
 
     @Test
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt
index 7470289..c2ce346 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt
@@ -69,19 +69,14 @@
 
     @Test
     fun testCancellationExceptionDownstream() = runTest {
-        val flow = flow {
-            emit(1)
-            hang { expect(2) }
-        }.flatMapMerge {
+        val flow = flowOf(1, 2, 3).flatMapMerge {
             flow {
                 emit(it)
-                expect(1)
                 throw CancellationException("")
             }
         }
 
-        assertFailsWith<CancellationException>(flow)
-        finish(3)
+        assertEquals(listOf(1, 2, 3), flow.toList())
     }
 
     @Test
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt
index 084af5b..4ec7cc3 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt
@@ -36,4 +36,17 @@
         consumer.cancelAndJoin()
         finish(2)
     }
+
+    @Test
+    fun testCancellation() = runTest {
+        val flow = flow {
+            repeat(5) {
+                emit(flow {
+                    if (it == 2) throw CancellationException("")
+                    emit(1)
+                })
+            }
+        }
+        assertFailsWith<CancellationException>(flow.flattenConcat())
+    }
 }
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt
index 6865328..8fba845 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt
@@ -83,7 +83,7 @@
         }.map {
             expect(2)
             assertEquals("throwing", it)
-            throw TestException(); it
+            throw TestException()
         }.flowOn(NamedDispatchers("throwing"))
 
         assertFailsWith<TestException>(flow)
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt
index 893811d..d8bb480 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt
@@ -39,10 +39,9 @@
                 }
                 emit(1)
             }
-        }.mapNotNull {
+        }.mapNotNull<Int, Int> {
             latch.receive()
             throw TestException()
-            it + 1
         }.catch { emit(42) }
 
         assertEquals(42, flow.single())
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/MergeTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/MergeTest.kt
index 1248188..f084798 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/MergeTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/MergeTest.kt
@@ -46,6 +46,64 @@
     }
 
     @Test
+    fun testOneSourceCancelled() = runTest {
+        val flow = flow {
+            expect(1)
+            emit(1)
+            expect(2)
+            yield()
+            throw CancellationException("")
+        }
+
+        val otherFlow = flow {
+            repeat(5) {
+                emit(1)
+                yield()
+            }
+
+            expect(3)
+        }
+
+        val result = listOf(flow, otherFlow).merge().toList()
+        assertEquals(MutableList(6) { 1 }, result)
+        finish(4)
+    }
+
+    @Test
+    fun testOneSourceCancelledNonFused() = runTest {
+        val flow = flow {
+            expect(1)
+            emit(1)
+            expect(2)
+            yield()
+            throw CancellationException("")
+        }
+
+        val otherFlow = flow {
+            repeat(5) {
+                emit(1)
+                yield()
+            }
+
+            expect(3)
+        }
+
+        val result = listOf(flow, otherFlow).nonFuseableMerge().toList()
+        assertEquals(MutableList(6) { 1 }, result)
+        finish(4)
+    }
+
+    private fun <T> Iterable<Flow<T>>.nonFuseableMerge(): Flow<T> {
+        return channelFlow {
+            forEach { flow ->
+                launch {
+                    flow.collect { send(it) }
+                }
+            }
+        }
+    }
+
+    @Test
     fun testIsolatedContext() = runTest {
         val flow = flow {
             emit(NamedDispatchers.name())
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt
index b8a6b19..e5dde1b 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt
@@ -104,4 +104,61 @@
         job.cancelAndJoin()
         finish(3)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testUpstreamExceptionConcurrentWithDownstream() = runTest {
+        val flow = flow {
+            try {
+                expect(1)
+                emit(1)
+            } finally {
+                expect(3)
+                throw TestException()
+            }
+        }.retry { expectUnreached(); true }.onEach {
+            expect(2)
+            throw TestException2()
+        }
+
+        assertFailsWith<TestException>(flow)
+        finish(4)
+    }
+
+    @Test
+    fun testUpstreamExceptionConcurrentWithDownstreamCancellation() = runTest {
+        val flow = flow {
+            try {
+                expect(1)
+                emit(1)
+            } finally {
+                expect(3)
+                throw TestException()
+            }
+        }.retry { expectUnreached(); true }.onEach {
+            expect(2)
+            throw CancellationException("")
+        }
+
+        assertFailsWith<TestException>(flow)
+        finish(4)
+    }
+
+    @Test
+    fun testUpstreamCancellationIsIgnoredWhenDownstreamFails() = runTest {
+        val flow = flow {
+            try {
+                expect(1)
+                emit(1)
+            } finally {
+                expect(3)
+                throw CancellationException("")
+            }
+        }.retry { expectUnreached(); true }.onEach {
+            expect(2)
+            throw TestException("")
+        }
+
+        assertFailsWith<TestException>(flow)
+        finish(4)
+    }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
index 87bee56..3c04abd 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
@@ -9,6 +9,7 @@
 import kotlinx.coroutines.flow.*
 import kotlin.test.*
 import kotlin.time.*
+import kotlin.time.Duration.Companion.milliseconds
 
 class SampleTest : TestBase() {
     @Test
@@ -250,7 +251,6 @@
             expect(2)
             yield()
             throw TestException()
-            it
         }
 
         assertFailsWith<TestException>(flow)
@@ -274,26 +274,25 @@
         finish(4)
     }
 
-    @ExperimentalTime
     @Test
-    public fun testDurationBasic() = withVirtualTime {
+    fun testDurationBasic() = withVirtualTime {
         expect(1)
         val flow = flow {
             expect(3)
             emit("A")
-            delay(Duration.milliseconds(1500))
+            delay(1500.milliseconds)
             emit("B")
-            delay(Duration.milliseconds(500))
+            delay(500.milliseconds)
             emit("C")
-            delay(Duration.milliseconds(250))
+            delay(250.milliseconds)
             emit("D")
-            delay(Duration.milliseconds(2000))
+            delay(2000.milliseconds)
             emit("E")
             expect(4)
         }
 
         expect(2)
-        val result = flow.sample(Duration.milliseconds(1000)).toList()
+        val result = flow.sample(1000.milliseconds).toList()
         assertEquals(listOf("A", "B", "D"), result)
         finish(5)
     }
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TakeTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TakeTest.kt
index 62d2322..ea8939f 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/TakeTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/TakeTest.kt
@@ -88,9 +88,8 @@
                 emit(1)
             }
         }.take(2)
-            .map {
+            .map<Int, Int> {
                 throw TestException()
-                42
             }.catch { emit(42) }
 
         assertEquals(42, flow.single())
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInConflationTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInConflationTest.kt
index 0528e97..c19d523 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInConflationTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInConflationTest.kt
@@ -21,7 +21,7 @@
         op: suspend Flow<Int>.(CoroutineScope) -> Flow<Int>
     ) = runTest {
         expect(1)
-        // emit all and conflate, then should collect bufferCapacity latest ones
+        // emit all and conflate, then should collect bufferCapacity the latest ones
         val done = Job()
         flow {
             repeat(n) { i ->
@@ -159,4 +159,4 @@
         checkConflation(1, BufferOverflow.DROP_LATEST) {
             buffer(23).buffer(onBufferOverflow = BufferOverflow.DROP_LATEST).shareIn(it, SharingStarted.Eagerly, 0)
         }
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt
index db69e2b..cf83a50 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt
@@ -210,4 +210,30 @@
             stop()
         }
     }
+
+    @Test
+    fun testShouldStart() = runTest {
+        val flow = flow {
+            expect(2)
+            emit(1)
+            expect(3)
+        }.shareIn(this, SharingStarted.Lazily)
+
+        expect(1)
+        flow.onSubscription { throw CancellationException("") }
+            .catch { e -> assertTrue { e is CancellationException } }
+            .collect()
+        yield()
+        finish(4)
+    }
+
+    @Test
+    fun testShouldStartScalar() = runTest {
+        val j = Job()
+        val shared = flowOf(239).stateIn(this + j, SharingStarted.Lazily, 42)
+        assertEquals(42, shared.first())
+        yield()
+        assertEquals(239, shared.first())
+        j.cancel()
+    }
 }
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
index 6e18b38..98e04f0 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
@@ -798,4 +798,24 @@
         job.join()
         finish(5)
     }
+
+    @Test
+    fun testSubscriptionCount() = runTest {
+        val flow = MutableSharedFlow<Int>()
+        fun startSubscriber() = launch(start = CoroutineStart.UNDISPATCHED) { flow.collect() }
+
+        assertEquals(0, flow.subscriptionCount.first())
+
+        val j1 = startSubscriber()
+        assertEquals(1, flow.subscriptionCount.first())
+
+        val j2 = startSubscriber()
+        assertEquals(2, flow.subscriptionCount.first())
+
+        j1.cancelAndJoin()
+        assertEquals(1, flow.subscriptionCount.first())
+
+        j2.cancelAndJoin()
+        assertEquals(0, flow.subscriptionCount.first())
+    }
 }
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
index 516bb2e..da2f3e5 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
@@ -7,6 +7,8 @@
 import kotlinx.coroutines.*
 import kotlin.test.*
 import kotlin.time.*
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 
 class SharingStartedWhileSubscribedTest : TestBase() {
     @Test // make sure equals works properly, or otherwise other tests don't make sense
@@ -26,19 +28,52 @@
         }
     }
 
-    @OptIn(ExperimentalTime::class)
     @Test
     fun testDurationParams() {
         assertEquals(SharingStarted.WhileSubscribed(0), SharingStarted.WhileSubscribed(Duration.ZERO))
-        assertEquals(SharingStarted.WhileSubscribed(10), SharingStarted.WhileSubscribed(Duration.milliseconds(10)))
+        assertEquals(SharingStarted.WhileSubscribed(10), SharingStarted.WhileSubscribed(10.milliseconds))
         assertEquals(SharingStarted.WhileSubscribed(1000), SharingStarted.WhileSubscribed(1.seconds))
         assertEquals(SharingStarted.WhileSubscribed(Long.MAX_VALUE), SharingStarted.WhileSubscribed(Duration.INFINITE))
         assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 0), SharingStarted.WhileSubscribed(replayExpiration = Duration.ZERO))
         assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 3), SharingStarted.WhileSubscribed(
-            replayExpiration = Duration.milliseconds(3)
+            replayExpiration = 3.milliseconds
         ))
-        assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 7000), SharingStarted.WhileSubscribed(replayExpiration = 7.seconds))
+        assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 7000),
+            SharingStarted.WhileSubscribed(replayExpiration = 7.seconds))
         assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = Long.MAX_VALUE), SharingStarted.WhileSubscribed(replayExpiration = Duration.INFINITE))
     }
-}
 
+    @Test
+    fun testShouldRestart() = runTest {
+        var started = 0
+        val flow = flow {
+            expect(1 + ++started)
+            emit(1)
+            hang {  }
+        }.shareIn(this, SharingStarted.WhileSubscribed(100 /* ms */))
+
+        expect(1)
+        flow.first()
+        delay(200)
+        flow.first()
+        finish(4)
+        coroutineContext.job.cancelChildren()
+    }
+
+    @Test
+    fun testImmediateUnsubscribe() = runTest {
+        val flow = flow {
+            expect(2)
+            emit(1)
+            hang { finish(4) }
+        }.shareIn(this, SharingStarted.WhileSubscribed(400, 0 /* ms */), 1)
+
+        expect(1)
+        repeat(5) {
+            flow.first()
+            delay(100)
+        }
+        expect(3)
+        coroutineContext.job.cancelChildren()
+    }
+}
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
index 26d6f80..62e62e5 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
@@ -7,22 +7,23 @@
 import kotlinx.coroutines.*
 import kotlin.test.*
 import kotlin.time.*
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 
-@ExperimentalTime
 class SelectTimeoutDurationTest : TestBase() {
     @Test
     fun testBasic() = runTest {
         expect(1)
         val result = select<String> {
-            onTimeout(Duration.milliseconds(1000)) {
+            onTimeout(1000.milliseconds) {
                 expectUnreached()
                 "FAIL"
             }
-            onTimeout(Duration.milliseconds(100)) {
+            onTimeout(100.milliseconds) {
                 expect(2)
                 "OK"
             }
-            onTimeout(Duration.milliseconds(500)) {
+            onTimeout(500.milliseconds) {
                 expectUnreached()
                 "FAIL"
             }
@@ -35,7 +36,7 @@
     fun testZeroTimeout() = runTest {
         expect(1)
         val result = select<String> {
-            onTimeout(Duration.seconds(1)) {
+            onTimeout(1.seconds) {
                 expectUnreached()
                 "FAIL"
             }
@@ -52,11 +53,11 @@
     fun testNegativeTimeout() = runTest {
         expect(1)
         val result = select<String> {
-            onTimeout(Duration.seconds(1)) {
+            onTimeout(1.seconds) {
                 expectUnreached()
                 "FAIL"
             }
-            onTimeout(-Duration.milliseconds(10)) {
+            onTimeout(-10.milliseconds) {
                 expect(2)
                 "OK"
             }
@@ -71,13 +72,13 @@
         val iterations =10_000
         for (i in 0..iterations) {
             val result = selectUnbiased<Int> {
-                onTimeout(-Duration.seconds(1)) {
+                onTimeout((-1).seconds) {
                     0
                 }
                 onTimeout(Duration.ZERO) {
                     1
                 }
-                onTimeout(Duration.seconds(1)) {
+                onTimeout(1.seconds) {
                     expectUnreached()
                     2
                 }
diff --git a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt
new file mode 100644
index 0000000..8a6c092
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+/**
+ * Runs a new coroutine and **blocks** the current thread 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.
+ */
+public expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt b/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt
similarity index 100%
rename from kotlinx-coroutines-core/jvm/src/CompletionHandler.kt
rename to kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt
diff --git a/kotlinx-coroutines-core/concurrent/src/MultithreadedDispatchers.common.kt b/kotlinx-coroutines-core/concurrent/src/MultithreadedDispatchers.common.kt
new file mode 100644
index 0000000..a2b4241
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/src/MultithreadedDispatchers.common.kt
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+@ExperimentalCoroutinesApi
+public expect fun newSingleThreadContext(name: String): CloseableCoroutineDispatcher
+
+@ExperimentalCoroutinesApi
+public expect fun newFixedThreadPoolContext(nThreads: Int, name: String): CloseableCoroutineDispatcher
diff --git a/kotlinx-coroutines-core/concurrent/src/channels/Channels.kt b/kotlinx-coroutines-core/concurrent/src/channels/Channels.kt
new file mode 100644
index 0000000..24422b5
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/src/channels/Channels.kt
@@ -0,0 +1,60 @@
+@file:JvmMultifileClass
+@file:JvmName("ChannelsKt")
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Adds [element] to this channel, **blocking** the caller while this channel is full,
+ * and returning either [successful][ChannelResult.isSuccess] result when the element was added, or
+ * failed result representing closed channel with a corresponding exception.
+ *
+ * This is a way to call [Channel.send] method in a safe manner inside a blocking code using [runBlocking] and catching,
+ * so this function should not be used from coroutine.
+ *
+ * Example of usage:
+ *
+ * ```
+ * // From callback API
+ * channel.trySendBlocking(element)
+ *     .onSuccess { /* request next element or debug log */ }
+ *     .onFailure { t: Throwable? -> /* throw or log */ }
+ * ```
+ *
+ * For this operation it is guaranteed that [failure][ChannelResult.failed] always contains an exception in it.
+ *
+ * @throws `InterruptedException` on JVM if the current thread is interrupted during the blocking send operation.
+ */
+public fun <E> SendChannel<E>.trySendBlocking(element: E): ChannelResult<Unit> {
+    /*
+     * Sent successfully -- bail out.
+     * But failure may indicate either that the channel it full or that
+     * it is close. Go to slow path on failure to simplify the successful path and
+     * to materialize default exception.
+     */
+    trySend(element).onSuccess { return ChannelResult.success(Unit) }
+    return runBlocking {
+        val r = runCatching { send(element) }
+        if (r.isSuccess) ChannelResult.success(Unit)
+        else ChannelResult.closed(r.exceptionOrNull())
+    }
+}
+
+/** @suppress */
+@Deprecated(
+    level = DeprecationLevel.ERROR,
+    message = "Deprecated in the favour of 'trySendBlocking'. " +
+        "Consider handling the result of 'trySendBlocking' explicitly and rethrow exception if necessary",
+    replaceWith = ReplaceWith("trySendBlocking(element)")
+) // WARNING in 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
+public fun <E> SendChannel<E>.sendBlocking(element: E) {
+    // fast path
+    if (trySend(element).isSuccess)
+        return
+    // slow path
+    runBlocking {
+        send(element)
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt
similarity index 98%
rename from kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
rename to kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt
index 9bbc6dc..b4b36da 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
+++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt
@@ -7,6 +7,8 @@
 
 import kotlinx.atomicfu.*
 import kotlinx.coroutines.*
+import kotlin.jvm.*
+import kotlin.native.concurrent.*
 
 private typealias Node = LockFreeLinkedListNode
 
@@ -20,9 +22,11 @@
 internal const val FAILURE: Int = 2
 
 @PublishedApi
+@SharedImmutable
 internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE")
 
 @PublishedApi
+@SharedImmutable
 internal val LIST_EMPTY: Any = Symbol("LIST_EMPTY")
 
 /** @suppress **This is unstable API and it is subject to change.** */
@@ -616,7 +620,7 @@
         assert { next === this._next.value }
     }
 
-    override fun toString(): String = "${this::class.java.simpleName}@${Integer.toHexString(System.identityHashCode(this))}"
+    override fun toString(): String = "${this::classSimpleName}@${this.hexAddress}"
 }
 
 private class Removed(@JvmField val ref: Node) {
@@ -646,7 +650,7 @@
     }
 
     // just a defensive programming -- makes sure that list head sentinel is never removed
-    public actual final override fun remove(): Boolean = error("head cannot be removed")
+    public actual final override fun remove(): Nothing = error("head cannot be removed")
 
     // optimization: because head is never removed, we don't have to read _next.value to check these:
     override val isRemoved: Boolean get() = false
diff --git a/kotlinx-coroutines-core/concurrent/test/AbstractDispatcherConcurrencyTest.kt b/kotlinx-coroutines-core/concurrent/test/AbstractDispatcherConcurrencyTest.kt
new file mode 100644
index 0000000..8fc4f4e
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/AbstractDispatcherConcurrencyTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines
+
+import kotlinx.coroutines.channels.*
+import kotlin.test.*
+
+
+abstract class AbstractDispatcherConcurrencyTest : TestBase() {
+
+   public abstract val dispatcher: CoroutineDispatcher
+
+    @Test
+    fun testLaunchAndJoin() = runMtTest {
+        expect(1)
+        var capturedMutableState = 0
+        val job = GlobalScope.launch(dispatcher) {
+            ++capturedMutableState
+            expect(2)
+        }
+        runBlocking { job.join() }
+        assertEquals(1, capturedMutableState)
+        finish(3)
+    }
+
+    @Test
+    fun testDispatcherHasOwnThreads() = runMtTest {
+        val channel = Channel<Int>()
+        GlobalScope.launch(dispatcher) {
+            channel.send(42)
+        }
+
+        var result = ChannelResult.failure<Int>()
+        while (!result.isSuccess) {
+            result = channel.tryReceive()
+            // Block the thread, wait
+        }
+        // Delivery was successful, let's check it
+        assertEquals(42, result.getOrThrow())
+    }
+
+    @Test
+    fun testDelayInDispatcher() = runMtTest {
+        expect(1)
+        val job = GlobalScope.launch(dispatcher) {
+            expect(2)
+            delay(100)
+            expect(3)
+        }
+        runBlocking { job.join() }
+        finish(4)
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt b/kotlinx-coroutines-core/concurrent/test/AtomicCancellationTest.kt
similarity index 98%
rename from kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt
rename to kotlinx-coroutines-core/concurrent/test/AtomicCancellationTest.kt
index 2612b84..74751fc 100644
--- a/kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/AtomicCancellationTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines
@@ -142,4 +142,4 @@
         yield() // to jobToJoin & canceller
         expect(6)
     }
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/concurrent/test/ConcurrentExceptionsStressTest.kt b/kotlinx-coroutines-core/concurrent/test/ConcurrentExceptionsStressTest.kt
new file mode 100644
index 0000000..d4252da
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/ConcurrentExceptionsStressTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.exceptions.*
+import kotlinx.coroutines.internal.*
+import kotlin.test.*
+
+class ConcurrentExceptionsStressTest : TestBase() {
+    private val nWorkers = 4
+    private val nRepeat = 1000 * stressTestMultiplier
+
+    private var workers: Array<CloseableCoroutineDispatcher> = emptyArray()
+
+    @AfterTest
+    fun tearDown() {
+        workers.forEach {
+            it.close()
+        }
+    }
+
+    @Test
+    fun testStress() = runMtTest {
+        workers = Array(nWorkers) { index ->
+            newSingleThreadContext("JobExceptionsStressTest-$index")
+        }
+
+        repeat(nRepeat) {
+            testOnce()
+        }
+    }
+
+    @Suppress("SuspendFunctionOnCoroutineScope") // workaround native inline fun stacktraces
+    private suspend fun CoroutineScope.testOnce() {
+        val deferred = async(NonCancellable) {
+            repeat(nWorkers) { index ->
+                // Always launch a coroutine even if parent job was already cancelled (atomic start)
+                launch(workers[index], start = CoroutineStart.ATOMIC) {
+                    randomWait()
+                    throw StressException(index)
+                }
+            }
+        }
+        deferred.join()
+        assertTrue(deferred.isCancelled)
+        val completionException = deferred.getCompletionExceptionOrNull()
+        val cause = completionException as? StressException
+            ?: unexpectedException("completion", completionException)
+        val suppressed = cause.suppressed
+        val indices = listOf(cause.index) + suppressed.mapIndexed { index, e ->
+            (e as? StressException)?.index ?: unexpectedException("suppressed $index", e)
+        }
+        repeat(nWorkers) { index ->
+            assertTrue(index in indices, "Exception $index is missing: $indices")
+        }
+        assertEquals(nWorkers, indices.size, "Duplicated exceptions in list: $indices")
+    }
+
+    private fun unexpectedException(msg: String, e: Throwable?): Nothing {
+        throw IllegalStateException("Unexpected $msg exception", e)
+    }
+
+    private class StressException(val index: Int) : SuppressSupportingThrowable()
+}
+
diff --git a/kotlinx-coroutines-core/concurrent/test/ConcurrentTestUtilities.common.kt b/kotlinx-coroutines-core/concurrent/test/ConcurrentTestUtilities.common.kt
new file mode 100644
index 0000000..a4d40fb
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/ConcurrentTestUtilities.common.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+
+internal expect open class SuppressSupportingThrowable() : Throwable
+expect val Throwable.suppressed: Array<Throwable>
+expect fun Throwable.printStackTrace()
+
+expect fun randomWait()
+
+expect fun currentThreadName(): String
+
+inline fun CloseableCoroutineDispatcher.use(block: (CloseableCoroutineDispatcher) -> Unit) {
+    try {
+        block(this)
+    } finally {
+        close()
+    }
+}
diff --git a/kotlinx-coroutines-core/concurrent/test/DefaultDispatcherConcurrencyTest.kt b/kotlinx-coroutines-core/concurrent/test/DefaultDispatcherConcurrencyTest.kt
new file mode 100644
index 0000000..a12930c
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/DefaultDispatcherConcurrencyTest.kt
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines
+
+class DefaultDispatcherConcurrencyTest : AbstractDispatcherConcurrencyTest() {
+    override val dispatcher: CoroutineDispatcher = Dispatchers.Default
+}
diff --git a/kotlinx-coroutines-core/jvm/test/JobStructuredJoinStressTest.kt b/kotlinx-coroutines-core/concurrent/test/JobStructuredJoinStressTest.kt
similarity index 86%
rename from kotlinx-coroutines-core/jvm/test/JobStructuredJoinStressTest.kt
rename to kotlinx-coroutines-core/concurrent/test/JobStructuredJoinStressTest.kt
index 50d86f3..431bb69 100644
--- a/kotlinx-coroutines-core/jvm/test/JobStructuredJoinStressTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/JobStructuredJoinStressTest.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines
 
-import org.junit.*
 import kotlin.coroutines.*
+import kotlin.test.*
 
 /**
  * Test a race between job failure and join.
@@ -16,12 +16,12 @@
     private val nRepeats = 10_000 * stressTestMultiplier
 
     @Test
-    fun testStressRegularJoin() {
+    fun testStressRegularJoin() = runMtTest {
         stress(Job::join)
     }
 
     @Test
-    fun testStressSuspendCancellable() {
+    fun testStressSuspendCancellable() = runMtTest {
         stress { job ->
             suspendCancellableCoroutine { cont ->
                 job.invokeOnCompletion { cont.resume(Unit) }
@@ -30,7 +30,7 @@
     }
 
     @Test
-    fun testStressSuspendCancellableReusable() {
+    fun testStressSuspendCancellableReusable() = runMtTest {
         stress { job ->
             suspendCancellableCoroutineReusable { cont ->
                 job.invokeOnCompletion { cont.resume(Unit) }
@@ -61,4 +61,4 @@
         }
         finish(2 + nRepeats)
     }
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt b/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt
new file mode 100644
index 0000000..8d38f05
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2022 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.*
+import kotlinx.coroutines.exceptions.*
+import kotlin.test.*
+
+class LimitedParallelismConcurrentTest : TestBase() {
+
+    private val targetParallelism = 4
+    private val iterations = 100_000
+    private val parallelism = atomic(0)
+
+    private fun checkParallelism() {
+        val value = parallelism.incrementAndGet()
+        randomWait()
+        assertTrue { value <= targetParallelism }
+        parallelism.decrementAndGet()
+    }
+
+    @Test
+    fun testLimitedExecutor() = runMtTest {
+        val executor = newFixedThreadPoolContext(targetParallelism, "test")
+        val view = executor.limitedParallelism(targetParallelism)
+        doStress {
+            repeat(iterations) {
+                launch(view) {
+                    checkParallelism()
+                }
+            }
+        }
+        executor.close()
+    }
+
+    private suspend inline fun doStress(crossinline block: suspend CoroutineScope.() -> Unit) {
+        repeat(stressTestMultiplier) {
+            coroutineScope {
+                block()
+            }
+        }
+    }
+
+    @Test
+    fun testTaskFairness() = runMtTest {
+        val executor = newSingleThreadContext("test")
+        val view = executor.limitedParallelism(1)
+        val view2 = executor.limitedParallelism(1)
+        val j1 = launch(view) {
+            while (true) {
+                yield()
+            }
+        }
+        val j2 = launch(view2) { j1.cancel() }
+        joinAll(j1, j2)
+        executor.close()
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt b/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt
similarity index 66%
rename from kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt
rename to kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt
index de38df6..70f6b8b 100644
--- a/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt
@@ -1,16 +1,16 @@
 /*
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
-
 package kotlinx.coroutines
 
+import kotlinx.coroutines.exceptions.*
 import kotlin.coroutines.*
 import kotlin.test.*
 
 class RunBlockingTest : TestBase() {
 
     @Test
-    fun testWithTimeoutBusyWait() = runBlocking {
+    fun testWithTimeoutBusyWait() = runMtTest {
         val value = withTimeoutOrNull(10) {
             while (isActive) {
                 // Busy wait
@@ -52,14 +52,14 @@
     }
 
     @Test
-    fun testOtherDispatcher() {
+    fun testOtherDispatcher() = runMtTest {
         expect(1)
         val name = "RunBlockingTest.testOtherDispatcher"
         val thread = newSingleThreadContext(name)
         runBlocking(thread) {
             expect(2)
             assertSame(coroutineContext[ContinuationInterceptor], thread)
-            assertTrue(Thread.currentThread().name.contains(name))
+            assertTrue(currentThreadName().contains(name))
             yield() // should work
             expect(3)
         }
@@ -67,19 +67,20 @@
         thread.close()
     }
 
-
     @Test
-    fun testCancellation() = newFixedThreadPoolContext(2, "testCancellation").use {
-        val job = GlobalScope.launch(it) {
-            runBlocking(coroutineContext) {
-                while (true) {
-                    yield()
+    fun testCancellation()  = runMtTest {
+        newFixedThreadPoolContext(2, "testCancellation").use {
+            val job = GlobalScope.launch(it) {
+                runBlocking(coroutineContext) {
+                    while (true) {
+                        yield()
+                    }
                 }
             }
-        }
 
-        runBlocking {
-            job.cancelAndJoin()
+            runBlocking {
+                job.cancelAndJoin()
+            }
         }
     }
 
@@ -104,40 +105,44 @@
         }
     }
 
-    @Test(expected = CancellationException::class)
-    fun testDispatchOnShutdown() = runBlocking<Unit> {
-        expect(1)
-        val job = launch(NonCancellable) {
-            try {
-                expect(2)
-                delay(Long.MAX_VALUE)
-            } finally {
-                finish(4)
+    @Test
+    fun testDispatchOnShutdown(): Unit = assertFailsWith<CancellationException> {
+        runBlocking {
+            expect(1)
+            val job = launch(NonCancellable) {
+                try {
+                    expect(2)
+                    delay(Long.MAX_VALUE)
+                } finally {
+                    finish(4)
+                }
             }
+
+            yield()
+            expect(3)
+            coroutineContext.cancel()
+            job.cancel()
         }
+    }.let { }
 
-        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)
+    @Test
+    fun testDispatchOnShutdown2(): Unit = assertFailsWith<CancellationException> {
+        runBlocking {
+            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()
-    }
+            expect(3)
+            job.cancel()
+        }
+    }.let { }
 
     @Test
     fun testNestedRunBlocking() = runBlocking {
@@ -157,22 +162,13 @@
     fun testIncompleteState() {
         val handle = runBlocking {
             // See #835
-            coroutineContext[Job]!!.invokeOnCompletion {  }
+            coroutineContext[Job]!!.invokeOnCompletion { }
         }
 
         handle.dispose()
     }
 
     @Test
-    fun testContract() {
-        val rb: Int
-        runBlocking {
-            rb = 42
-        }
-        rb.hashCode() // unused
-    }
-
-    @Test
     fun testCancelledParent() {
         val job = Job()
         job.cancel()
diff --git a/kotlinx-coroutines-core/concurrent/test/TestBaseExtension.common.kt b/kotlinx-coroutines-core/concurrent/test/TestBaseExtension.common.kt
new file mode 100644
index 0000000..b19bf50
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/TestBaseExtension.common.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines
+
+expect fun TestBase.runMtTest(
+    expected: ((Throwable) -> Boolean)? = null,
+    unhandled: List<(Throwable) -> Boolean> = emptyList(),
+    block: suspend CoroutineScope.() -> Unit
+): TestResult
diff --git a/kotlinx-coroutines-core/concurrent/test/channels/BroadcastChannelSubStressTest.kt b/kotlinx-coroutines-core/concurrent/test/channels/BroadcastChannelSubStressTest.kt
new file mode 100644
index 0000000..30b1075
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/channels/BroadcastChannelSubStressTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.channels
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+/**
+ * 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.
+ */
+class BroadcastChannelSubStressTest: TestBase() {
+
+    private val nSeconds = 5 * stressTestMultiplier
+    private val sentTotal = atomic(0L)
+    private val receivedTotal = atomic(0L)
+
+    @Test
+    fun testStress() = runMtTest {
+        TestBroadcastChannelKind.values().forEach { kind ->
+            println("--- BroadcastChannelSubStressTest $kind")
+            val broadcast = kind.create<Long>()
+            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.value
+                println("${sec + 1}: Sent $curSent, received ${receivedTotal.value}")
+                check(curSent > prevSent) { "Send stalled at $curSent events" }
+                prevSent = curSent
+            }
+            withTimeout(5000) {
+                sender.cancelAndJoin()
+                receiver.cancelAndJoin()
+            }
+        }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelCancelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt
similarity index 89%
rename from kotlinx-coroutines-core/jvm/test/channels/ChannelCancelUndeliveredElementStressTest.kt
rename to kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt
index 86adfee..3e38eec 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelCancelUndeliveredElementStressTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt
@@ -4,14 +4,14 @@
 
 package kotlinx.coroutines.channels
 
+import kotlinx.atomicfu.*
 import kotlinx.coroutines.*
 import kotlinx.coroutines.selects.*
-import java.util.concurrent.atomic.*
 import kotlin.random.*
 import kotlin.test.*
 
 class ChannelCancelUndeliveredElementStressTest : TestBase() {
-    private val repeatTimes = 10_000 * stressTestMultiplier
+    private val repeatTimes = (if (isNative) 1_000 else 10_000) * stressTestMultiplier
 
     // total counters
     private var sendCnt = 0
@@ -25,10 +25,10 @@
     private var dSendExceptionCnt = 0
     private var dTrySendFailedCnt = 0
     private var dReceivedCnt = 0
-    private val dUndeliveredCnt = AtomicInteger()
+    private val dUndeliveredCnt = atomic(0)
 
     @Test
-    fun testStress() = runTest {
+    fun testStress() = runMtTest {
         repeat(repeatTimes) {
             val channel = Channel<Int>(1) { dUndeliveredCnt.incrementAndGet() }
             val j1 = launch(Dispatchers.Default) {
@@ -43,23 +43,23 @@
             joinAll(j1, j2)
 
             // All elements must be either received or undelivered (IN every run)
-            if (dSendCnt - dTrySendFailedCnt != dReceivedCnt + dUndeliveredCnt.get()) {
+            if (dSendCnt - dTrySendFailedCnt != dReceivedCnt + dUndeliveredCnt.value) {
                 println("          Send: $dSendCnt")
                 println("Send exception: $dSendExceptionCnt")
                 println("trySend failed: $dTrySendFailedCnt")
                 println("      Received: $dReceivedCnt")
-                println("   Undelivered: ${dUndeliveredCnt.get()}")
+                println("   Undelivered: ${dUndeliveredCnt.value}")
                 error("Failed")
             }
             trySendFailedCnt += dTrySendFailedCnt
             receivedCnt += dReceivedCnt
-            undeliveredCnt += dUndeliveredCnt.get()
+            undeliveredCnt += dUndeliveredCnt.value
             // clear for next run
             dSendCnt = 0
             dSendExceptionCnt = 0
             dTrySendFailedCnt = 0
             dReceivedCnt = 0
-            dUndeliveredCnt.set(0)
+            dUndeliveredCnt.value = 0
         }
         // Stats
         println("          Send: $sendCnt")
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt b/kotlinx-coroutines-core/concurrent/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
similarity index 70%
rename from kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
rename to kotlinx-coroutines-core/concurrent/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
index 2b3c05b..5da00d2 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
@@ -1,29 +1,28 @@
 /*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.channels
 
+import kotlinx.atomicfu.*
 import kotlinx.coroutines.*
-import org.junit.Test
-import java.util.concurrent.atomic.*
 import kotlin.test.*
 
 class ConflatedBroadcastChannelNotifyStressTest : TestBase() {
     private val nSenders = 2
     private val nReceivers = 3
-    private val nEvents = 500_000 * stressTestMultiplier
+    private val nEvents =  (if (isNative) 5_000 else 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()
+    private val sendersCompleted = atomic(0)
+    private val receiversCompleted = atomic(0)
+    private val sentTotal = atomic(0)
+    private val receivedTotal = atomic(0)
 
     @Test
-    fun testStressNotify()= runBlocking {
+    fun testStressNotify()= runMtTest {
         println("--- ConflatedBroadcastChannelNotifyStressTest")
         val senders = List(nSenders) { senderId ->
             launch(Dispatchers.Default + CoroutineName("Sender$senderId")) {
@@ -57,7 +56,7 @@
             var seconds = 0
             while (true) {
                 delay(1000)
-                println("${++seconds}: Sent ${sentTotal.get()}, received ${receivedTotal.get()}")
+                println("${++seconds}: Sent ${sentTotal.value}, received ${receivedTotal.value}")
             }
         }
         try {
@@ -71,13 +70,13 @@
         }
         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")
-        assertEquals(nSenders, sendersCompleted.get())
-        assertEquals(nReceivers, receiversCompleted.get())
-        assertEquals(nEvents, sentTotal.get())
+        println("Completed successfully ${sendersCompleted.value} sender coroutines")
+        println("Completed successfully ${receiversCompleted.value} receiver coroutines")
+        println("                  Sent ${sentTotal.value} events")
+        println("              Received ${receivedTotal.value} events")
+        assertEquals(nSenders, sendersCompleted.value)
+        assertEquals(nReceivers, receiversCompleted.value)
+        assertEquals(nEvents, sentTotal.value)
     }
 
     private suspend fun waitForEvent(): Int =
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt b/kotlinx-coroutines-core/concurrent/test/channels/TrySendBlockingTest.kt
similarity index 89%
rename from kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt
rename to kotlinx-coroutines-core/concurrent/test/channels/TrySendBlockingTest.kt
index 8512aeb..77c6518 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/channels/TrySendBlockingTest.kt
@@ -1,17 +1,16 @@
 /*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.channels
 
 import kotlinx.coroutines.*
-import org.junit.Test
 import kotlin.test.*
 
-class ChannelsJvmTest : TestBase() {
+class TrySendBlockingTest : TestBase() {
 
     @Test
-    fun testTrySendBlocking() {
+    fun testTrySendBlocking() = runBlocking<Unit> { // For old MM
         val ch = Channel<Int>()
         val sum = GlobalScope.async {
             var sum = 0
diff --git a/kotlinx-coroutines-core/jvm/test/flow/CombineStressTest.kt b/kotlinx-coroutines-core/concurrent/test/flow/CombineStressTest.kt
similarity index 89%
rename from kotlinx-coroutines-core/jvm/test/flow/CombineStressTest.kt
rename to kotlinx-coroutines-core/concurrent/test/flow/CombineStressTest.kt
index 3b5c36f..f262e78 100644
--- a/kotlinx-coroutines-core/jvm/test/flow/CombineStressTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/flow/CombineStressTest.kt
@@ -1,16 +1,16 @@
 /*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.flow
 
 import kotlinx.coroutines.*
-import org.junit.*
+import kotlin.test.*
 
 class CombineStressTest : TestBase() {
 
     @Test
-    public fun testCancellation() = runTest {
+    fun testCancellation() = runMtTest {
         withContext(Dispatchers.Default + CoroutineExceptionHandler { _, _ -> expectUnreached() }) {
             flow {
                 expect(1)
@@ -26,7 +26,7 @@
     }
 
     @Test
-    public fun testFailure() = runTest {
+    fun testFailure() = runMtTest {
         val innerIterations = 100 * stressTestMultiplierSqrt
         val outerIterations = 10 * stressTestMultiplierSqrt
         withContext(Dispatchers.Default + CoroutineExceptionHandler { _, _ -> expectUnreached() }) {
diff --git a/kotlinx-coroutines-core/jvm/test/flow/FlowCancellationTest.kt b/kotlinx-coroutines-core/concurrent/test/flow/FlowCancellationTest.kt
similarity index 82%
rename from kotlinx-coroutines-core/jvm/test/flow/FlowCancellationTest.kt
rename to kotlinx-coroutines-core/concurrent/test/flow/FlowCancellationTest.kt
index 269805f..286ba75 100644
--- a/kotlinx-coroutines-core/jvm/test/flow/FlowCancellationTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/flow/FlowCancellationTest.kt
@@ -1,17 +1,18 @@
 /*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.flow
 
 import kotlinx.coroutines.*
 import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
 import kotlin.test.*
 
 class FlowCancellationTest : TestBase() {
 
     @Test
-    fun testEmitIsCooperative() = runTest {
+    fun testEmitIsCooperative() = runMtTest {
         val latch = Channel<Unit>(1)
         val job = flow {
             expect(1)
@@ -28,7 +29,7 @@
     }
 
     @Test
-    fun testIsActiveOnCurrentContext() = runTest {
+    fun testIsActiveOnCurrentContext() = runMtTest {
         val latch = Channel<Unit>(1)
         val job = flow<Unit> {
             expect(1)
@@ -45,7 +46,7 @@
     }
 
     @Test
-    fun testFlowWithEmptyContext() = runTest {
+    fun testFlowWithEmptyContext() = runMtTest {
         expect(1)
         withEmptyContext {
             val flow = flow {
@@ -59,4 +60,4 @@
         }
         finish(4)
     }
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/concurrent/test/flow/StateFlowCommonStressTest.kt b/kotlinx-coroutines-core/concurrent/test/flow/StateFlowCommonStressTest.kt
new file mode 100644
index 0000000..f2fb41a
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/flow/StateFlowCommonStressTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlin.random.*
+import kotlin.test.*
+
+// A simplified version of StateFlowStressTest
+class StateFlowCommonStressTest : TestBase() {
+    private val state = MutableStateFlow<Long>(0)
+
+    @Test
+    fun testSingleEmitterAndCollector() = runMtTest {
+        var collected = 0L
+        val collector = launch(Dispatchers.Default) {
+            // collect, but abort and collect again after every 1000 values to stress allocation/deallocation
+            do {
+                val batchSize = Random.nextInt(1..1000)
+                var index = 0
+                val cnt = state.onEach { value ->
+                    // the first value in batch is allowed to repeat, but cannot go back
+                    val ok = if (index++ == 0) value >= collected else value > collected
+                    check(ok) {
+                        "Values must be monotonic, but $value is not, was $collected"
+                    }
+                    collected = value
+                }.take(batchSize).map { 1 }.sum()
+            } while (cnt == batchSize)
+        }
+
+        var current = 1L
+        val emitter = launch {
+            while (true) {
+                state.value = current++
+                if (current % 1000 == 0L) yield() // make it cancellable
+            }
+        }
+
+        delay(3000)
+        emitter.cancelAndJoin()
+        collector.cancelAndJoin()
+        assertTrue { current >= collected / 2 }
+    }
+}
diff --git a/kotlinx-coroutines-core/concurrent/test/flow/StateFlowUpdateCommonTest.kt b/kotlinx-coroutines-core/concurrent/test/flow/StateFlowUpdateCommonTest.kt
new file mode 100644
index 0000000..1e79709
--- /dev/null
+++ b/kotlinx-coroutines-core/concurrent/test/flow/StateFlowUpdateCommonTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.test.*
+
+// A simplified version of StateFlowUpdateStressTest
+class StateFlowUpdateCommonTest : TestBase() {
+    private val iterations = 100_000 * stressTestMultiplier
+
+    @Test
+    fun testUpdate() = doTest { update { it + 1 } }
+
+    @Test
+    fun testUpdateAndGet() = doTest { updateAndGet { it + 1 } }
+
+    @Test
+    fun testGetAndUpdate() = doTest { getAndUpdate { it + 1 } }
+
+    private fun doTest(increment: MutableStateFlow<Int>.() -> Unit) = runMtTest {
+        val flow = MutableStateFlow(0)
+        val j1 = launch(Dispatchers.Default) {
+            repeat(iterations / 2) {
+                flow.increment()
+            }
+        }
+
+        repeat(iterations / 2) {
+            flow.increment()
+        }
+
+        joinAll(j1)
+        assertEquals(iterations, flow.value)
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt b/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt
similarity index 96%
rename from kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt
rename to kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt
index b901144..7e85d49 100644
--- a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt
@@ -1,10 +1,9 @@
 /*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.internal
 
-import org.junit.Test
 import kotlin.test.*
 
 class LockFreeLinkedListTest {
@@ -82,4 +81,4 @@
         for (i in 0 until n) assertEquals(expected[i], actual[i], "item $i")
         assertEquals(expected.isEmpty(), list.isEmpty)
     }
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt b/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt
similarity index 65%
rename from kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt
rename to kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt
index 200cdc0..29c6c34 100644
--- a/kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.selects
@@ -11,61 +11,60 @@
 
 class SelectChannelStressTest: TestBase() {
 
+    // Running less iterations on native platforms because of some performance regression
+    private val iterations = (if (isNative) 1_000 else 1_000_000) * stressTestMultiplier
+
     @Test
-    fun testSelectSendResourceCleanupArrayChannel() = runTest {
+    fun testSelectSendResourceCleanupArrayChannel() = runMtTest {
         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 ->
+        repeat(iterations) { i ->
             select {
                 channel.onSend(i) { expectUnreached() }
                 default { expect(i + 2) }
             }
         }
-        finish(n + 2)
+        finish(iterations + 2)
     }
 
     @Test
-    fun testSelectReceiveResourceCleanupArrayChannel() = runTest {
+    fun testSelectReceiveResourceCleanupArrayChannel() = runMtTest {
         val channel = Channel<Int>(1)
-        val n = 10_000_000 * stressTestMultiplier
         expect(1)
-        repeat(n) { i ->
+        repeat(iterations) { i ->
             select {
                 channel.onReceive { expectUnreached() }
                 default { expect(i + 2) }
             }
         }
-        finish(n + 2)
+        finish(iterations + 2)
     }
 
     @Test
-    fun testSelectSendResourceCleanupRendezvousChannel() = runTest {
+    fun testSelectSendResourceCleanupRendezvousChannel() = runMtTest {
         val channel = Channel<Int>(Channel.RENDEZVOUS)
-        val n = 1_000_000 * stressTestMultiplier
         expect(1)
-        repeat(n) { i ->
+        repeat(iterations) { i ->
             select {
                 channel.onSend(i) { expectUnreached() }
                 default { expect(i + 2) }
             }
         }
-        finish(n + 2)
+        finish(iterations + 2)
     }
 
     @Test
-    fun testSelectReceiveResourceRendezvousChannel() = runTest {
+    fun testSelectReceiveResourceRendezvousChannel() = runMtTest {
         val channel = Channel<Int>(Channel.RENDEZVOUS)
-        val n = 1_000_000 * stressTestMultiplier
         expect(1)
-        repeat(n) { i ->
+        repeat(iterations) { i ->
             select {
                 channel.onReceive { expectUnreached() }
                 default { expect(i + 2) }
             }
         }
-        finish(n + 2)
+        finish(iterations + 2)
     }
 
     internal fun <R> SelectBuilder<R>.default(block: suspend () -> R) {
diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt b/kotlinx-coroutines-core/concurrent/test/selects/SelectMutexStressTest.kt
similarity index 93%
rename from kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt
rename to kotlinx-coroutines-core/concurrent/test/selects/SelectMutexStressTest.kt
index 5489ea5..8f649c2 100644
--- a/kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/selects/SelectMutexStressTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.selects
diff --git a/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt b/kotlinx-coroutines-core/concurrent/test/sync/MutexStressTest.kt
similarity index 73%
rename from kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt
rename to kotlinx-coroutines-core/concurrent/test/sync/MutexStressTest.kt
index 027f3c5..73b62ae 100644
--- a/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/sync/MutexStressTest.kt
@@ -1,22 +1,49 @@
 /*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.sync
 
 import kotlinx.coroutines.*
+import kotlinx.coroutines.exceptions.*
 import kotlinx.coroutines.selects.*
 import kotlin.test.*
 
 class MutexStressTest : TestBase() {
+
+    private val n = (if (isNative) 1_000 else 10_000) * stressTestMultiplier
+
     @Test
-    fun testStress() = runBlocking(Dispatchers.Default) {
-        val n = 1000 * stressTestMultiplier
+    fun testDefaultDispatcher() = runMtTest { testBody(Dispatchers.Default) }
+
+    @Test
+    fun testSingleThreadContext() = runMtTest {
+        newSingleThreadContext("testSingleThreadContext").use {
+            testBody(it)
+        }
+    }
+
+    @Test
+    fun testMultiThreadedContextWithSingleWorker() = runMtTest {
+        newFixedThreadPoolContext(1, "testMultiThreadedContextWithSingleWorker").use {
+            testBody(it)
+        }
+    }
+
+    @Test
+    fun testMultiThreadedContext() = runMtTest {
+       newFixedThreadPoolContext(8, "testMultiThreadedContext").use {
+            testBody(it)
+        }
+    }
+
+    @Suppress("SuspendFunctionOnCoroutineScope")
+    private suspend fun CoroutineScope.testBody(dispatcher: CoroutineDispatcher) {
         val k = 100
         var shared = 0
         val mutex = Mutex()
         val jobs = List(n) {
-            launch {
+            launch(dispatcher) {
                 repeat(k) {
                     mutex.lock()
                     shared++
@@ -29,11 +56,11 @@
     }
 
     @Test
-    fun stressUnlockCancelRace() = runTest {
+    fun stressUnlockCancelRace() = runMtTest {
         val n = 10_000 * stressTestMultiplier
         val mutex = Mutex(true) // create a locked mutex
         newSingleThreadContext("SemaphoreStressTest").use { pool ->
-            repeat (n) {
+            repeat(n) {
                 // Initially, we hold the lock and no one else can `lock`,
                 // otherwise it's a bug.
                 assertTrue(mutex.isLocked)
@@ -59,11 +86,11 @@
     }
 
     @Test
-    fun stressUnlockCancelRaceWithSelect() = runTest {
+    fun stressUnlockCancelRaceWithSelect() = runMtTest {
         val n = 10_000 * stressTestMultiplier
         val mutex = Mutex(true) // create a locked mutex
         newSingleThreadContext("SemaphoreStressTest").use { pool ->
-            repeat (n) {
+            repeat(n) {
                 // Initially, we hold the lock and no one else can `lock`,
                 // otherwise it's a bug.
                 assertTrue(mutex.isLocked)
@@ -92,7 +119,7 @@
     }
 
     @Test
-    fun testShouldBeUnlockedOnCancellation() = runTest {
+    fun testShouldBeUnlockedOnCancellation() = runMtTest {
         val mutex = Mutex()
         val n = 1000 * stressTestMultiplier
         repeat(n) {
diff --git a/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt b/kotlinx-coroutines-core/concurrent/test/sync/SemaphoreStressTest.kt
similarity index 69%
rename from kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt
rename to kotlinx-coroutines-core/concurrent/test/sync/SemaphoreStressTest.kt
index 2ceed64..c5f2038 100644
--- a/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/sync/SemaphoreStressTest.kt
@@ -1,18 +1,25 @@
+/*
+ * Copyright 2016-2021 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 org.junit.Test
+import kotlinx.coroutines.exceptions.*
 import kotlin.test.*
 
 class SemaphoreStressTest : TestBase() {
+
+    private val iterations = (if (isNative) 1_000 else 10_000) * stressTestMultiplier
+
     @Test
-    fun stressTestAsMutex() = runBlocking(Dispatchers.Default) {
-        val n = 10_000 * stressTestMultiplier
+    fun testStressTestAsMutex() = runMtTest {
+        val n = iterations
         val k = 100
         var shared = 0
         val semaphore = Semaphore(1)
         val jobs = List(n) {
-            launch {
+            launch(Dispatchers.Default) {
                 repeat(k) {
                     semaphore.acquire()
                     shared++
@@ -25,12 +32,12 @@
     }
 
     @Test
-    fun stressTest() = runBlocking(Dispatchers.Default) {
-        val n = 10_000 * stressTestMultiplier
+    fun testStress() = runMtTest {
+        val n = iterations
         val k = 100
         val semaphore = Semaphore(10)
         val jobs = List(n) {
-            launch {
+            launch(Dispatchers.Default) {
                 repeat(k) {
                     semaphore.acquire()
                     semaphore.release()
@@ -41,12 +48,33 @@
     }
 
     @Test
-    fun stressCancellation() = runBlocking(Dispatchers.Default) {
-        val n = 10_000 * stressTestMultiplier
+    fun testStressAsMutex() = runMtTest {
+        runBlocking(Dispatchers.Default) {
+            val n = iterations
+            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 testStressCancellation() = runMtTest {
+        val n = iterations
         val semaphore = Semaphore(1)
         semaphore.acquire()
         repeat(n) {
-            val job = launch {
+            val job = launch(Dispatchers.Default) {
                 semaphore.acquire()
             }
             yield()
@@ -62,8 +90,8 @@
      * the semaphore into an incorrect state where permits are leaked.
      */
     @Test
-    fun stressReleaseCancelRace() = runTest {
-        val n = 10_000 * stressTestMultiplier
+    fun testStressReleaseCancelRace() = runMtTest {
+        val n = iterations
         val semaphore = Semaphore(1, 1)
         newSingleThreadContext("SemaphoreStressTest").use { pool ->
             repeat (n) {
@@ -92,7 +120,7 @@
     }
 
     @Test
-    fun testShouldBeUnlockedOnCancellation() = runTest {
+    fun testShouldBeUnlockedOnCancellation() = runMtTest {
         val semaphore = Semaphore(1)
         val n = 1000 * stressTestMultiplier
         repeat(n) {
diff --git a/kotlinx-coroutines-core/js/src/CloseableCoroutineDispatcher.kt b/kotlinx-coroutines-core/js/src/CloseableCoroutineDispatcher.kt
new file mode 100644
index 0000000..0e239a4
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/CloseableCoroutineDispatcher.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+public actual abstract class CloseableCoroutineDispatcher actual constructor() : CoroutineDispatcher() {
+    public actual abstract fun close()
+}
diff --git a/kotlinx-coroutines-core/js/src/CoroutineContext.kt b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
index a98ea97..8036c88 100644
--- a/kotlinx-coroutines-core/js/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
@@ -12,7 +12,7 @@
 private const val UNDEFINED = "undefined"
 internal external val process: dynamic
 
-internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
+internal fun createDefaultDispatcher(): CoroutineDispatcher = when {
     // 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."
@@ -42,6 +42,10 @@
         combined + Dispatchers.Default else combined
 }
 
+public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext {
+    return this + addedContext
+}
+
 // No debugging facilities on JS
 internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block()
 internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block()
diff --git a/kotlinx-coroutines-core/js/src/Dispatchers.kt b/kotlinx-coroutines-core/js/src/Dispatchers.kt
index 8d3bac3..3eec540 100644
--- a/kotlinx-coroutines-core/js/src/Dispatchers.kt
+++ b/kotlinx-coroutines-core/js/src/Dispatchers.kt
@@ -8,8 +8,22 @@
 
 public actual object Dispatchers {
     public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
-    public actual val Main: MainCoroutineDispatcher = JsMainDispatcher(Default, false)
+    public actual val Main: MainCoroutineDispatcher
+        get() = injectedMainDispatcher ?: mainDispatcher
     public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
+
+    private val mainDispatcher = JsMainDispatcher(Default, false)
+    private var injectedMainDispatcher: MainCoroutineDispatcher? = null
+
+    @PublishedApi
+    internal fun injectMain(dispatcher: MainCoroutineDispatcher) {
+        injectedMainDispatcher = dispatcher
+    }
+
+    @PublishedApi
+    internal fun resetInjectedMain() {
+        injectedMainDispatcher = null
+    }
 }
 
 private class JsMainDispatcher(
diff --git a/kotlinx-coroutines-core/js/src/EventLoop.kt b/kotlinx-coroutines-core/js/src/EventLoop.kt
index b3a1364..13c3369 100644
--- a/kotlinx-coroutines-core/js/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/js/src/EventLoop.kt
@@ -25,3 +25,5 @@
 
 private fun unsupported(): Nothing =
     throw UnsupportedOperationException("runBlocking event loop is not supported")
+
+internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit) = block()
diff --git a/kotlinx-coroutines-core/js/src/JSDispatcher.kt b/kotlinx-coroutines-core/js/src/JSDispatcher.kt
index 6ad7d41..603005d 100644
--- a/kotlinx-coroutines-core/js/src/JSDispatcher.kt
+++ b/kotlinx-coroutines-core/js/src/JSDispatcher.kt
@@ -31,6 +31,11 @@
 
     abstract fun scheduleQueueProcessing()
 
+    override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+        parallelism.checkParallelism()
+        return this
+    }
+
     override fun dispatch(context: CoroutineContext, block: Runnable) {
         messageQueue.enqueue(block)
     }
diff --git a/kotlinx-coroutines-core/js/src/internal/Concurrent.kt b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt
index 0a1b031..71f6522 100644
--- a/kotlinx-coroutines-core/js/src/internal/Concurrent.kt
+++ b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt
@@ -16,3 +16,4 @@
 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/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
index 147b31d..d8c07f4 100644
--- a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
+++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
@@ -177,5 +177,5 @@
     }
 
     // just a defensive programming -- makes sure that list head sentinel is never removed
-    public final override fun remove(): Boolean = throw UnsupportedOperationException()
+    public final override fun remove(): Nothing = throw UnsupportedOperationException()
 }
diff --git a/kotlinx-coroutines-core/js/test/PromiseTest.kt b/kotlinx-coroutines-core/js/test/PromiseTest.kt
index cc1297c..6049a90 100644
--- a/kotlinx-coroutines-core/js/test/PromiseTest.kt
+++ b/kotlinx-coroutines-core/js/test/PromiseTest.kt
@@ -16,7 +16,7 @@
         val deferred = promise.asDeferred()
         assertEquals("OK", deferred.await())
     }
-    
+
     @Test
     fun testPromiseRejectedAsDeferred() = GlobalScope.promise {
         lateinit var promiseReject: (Throwable) -> Unit
diff --git a/kotlinx-coroutines-core/js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt
index cc7865b..c930c20 100644
--- a/kotlinx-coroutines-core/js/test/TestBase.kt
+++ b/kotlinx-coroutines-core/js/test/TestBase.kt
@@ -8,10 +8,13 @@
 
 public actual val isStressTest: Boolean = false
 public actual val stressTestMultiplier: Int = 1
+public actual val stressTestMultiplierSqrt: Int = 1
 
 @Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE")
 public actual typealias TestResult = Promise<Unit>
 
+public actual val isNative = false
+
 public actual open class TestBase actual constructor() {
     public actual val isBoundByJsTestTimeout = true
     private var actionIndex = 0
diff --git a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin
index 397aaf6..a6ea0a4 100644
--- a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin
+++ b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin
Binary files differ
diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro
new file mode 100644
index 0000000..c3911b8
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro
@@ -0,0 +1,31 @@
+# When editing this file, update the following files as well:
+# - META-INF/proguard/coroutines.pro
+# - META-INF/com.android.tools/r8/coroutines.pro
+
+# ServiceLoader support
+-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
+-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
+
+# Most of volatile fields are updated with AFU and should not be mangled
+-keepclassmembers class kotlinx.coroutines.** {
+    volatile <fields>;
+}
+
+# Same story for the standard library's SafeContinuation that also uses AtomicReferenceFieldUpdater
+-keepclassmembers class kotlin.coroutines.SafeContinuation {
+    volatile <fields>;
+}
+
+# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when
+# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used.
+-dontwarn java.lang.instrument.ClassFileTransformer
+-dontwarn sun.misc.SignalHandler
+-dontwarn java.lang.instrument.Instrumentation
+-dontwarn sun.misc.Signal
+
+# Only used in `kotlinx.coroutines.internal.ExceptionsConstructor`.
+# The case when it is not available is hidden in a `try`-`catch`, as well as a check for Android.
+-dontwarn java.lang.ClassValue
+
+# An annotation used for build tooling, won't be directly accessed.
+-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro
new file mode 100644
index 0000000..1ac5ce5
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro
@@ -0,0 +1,27 @@
+# When editing this file, update the following files as well:
+# - META-INF/proguard/coroutines.pro
+# - META-INF/com.android.tools/proguard/coroutines.pro
+
+# Most of volatile fields are updated with AFU and should not be mangled
+-keepclassmembers class kotlinx.coroutines.** {
+    volatile <fields>;
+}
+
+# Same story for the standard library's SafeContinuation that also uses AtomicReferenceFieldUpdater
+-keepclassmembers class kotlin.coroutines.SafeContinuation {
+    volatile <fields>;
+}
+
+# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when
+# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used.
+-dontwarn java.lang.instrument.ClassFileTransformer
+-dontwarn sun.misc.SignalHandler
+-dontwarn java.lang.instrument.Instrumentation
+-dontwarn sun.misc.Signal
+
+# Only used in `kotlinx.coroutines.internal.ExceptionsConstructor`.
+# The case when it is not available is hidden in a `try`-`catch`, as well as a check for Android.
+-dontwarn java.lang.ClassValue
+
+# An annotation used for build tooling, won't be directly accessed.
+-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
index 1a9ae1c..6d29ed2 100644
--- a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
+++ b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
@@ -1,3 +1,7 @@
+# When editing this file, update the following files as well:
+# - META-INF/com.android.tools/proguard/coroutines.pro
+# - META-INF/com.android.tools/r8/coroutines.pro
+
 # ServiceLoader support
 -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
 -keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
@@ -18,3 +22,10 @@
 -dontwarn sun.misc.SignalHandler
 -dontwarn java.lang.instrument.Instrumentation
 -dontwarn sun.misc.Signal
+
+# Only used in `kotlinx.coroutines.internal.ExceptionsConstructor`.
+# The case when it is not available is hidden in a `try`-`catch`, as well as a check for Android.
+-dontwarn java.lang.ClassValue
+
+# An annotation used for build tooling, won't be directly accessed.
+-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
diff --git a/kotlinx-coroutines-core/jvm/src/Builders.kt b/kotlinx-coroutines-core/jvm/src/Builders.kt
index edb4303..8c4b62b 100644
--- a/kotlinx-coroutines-core/jvm/src/Builders.kt
+++ b/kotlinx-coroutines-core/jvm/src/Builders.kt
@@ -35,7 +35,7 @@
  * @param block the coroutine code.
  */
 @Throws(InterruptedException::class)
-public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
+public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
     contract {
         callsInPlace(block, InvocationKind.EXACTLY_ONCE)
     }
diff --git a/kotlinx-coroutines-core/jvm/src/CommonPool.kt b/kotlinx-coroutines-core/jvm/src/CommonPool.kt
deleted file mode 100644
index 502630b..0000000
--- a/kotlinx-coroutines-core/jvm/src/CommonPool.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright 2016-2021 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.
-     */
-    private 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()
-            // CommonPool only rejects execution when it is being closed and this behavior is reserved
-            // for testing purposes, so we don't have to worry about cancelling the affected Job here.
-            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/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
index e91bb9f..7209bee 100644
--- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
@@ -5,38 +5,90 @@
 package kotlinx.coroutines
 
 import kotlinx.coroutines.internal.*
-import kotlinx.coroutines.scheduling.*
 import kotlin.coroutines.*
 import kotlin.coroutines.jvm.internal.CoroutineStackFrame
 
-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).
- *
+ * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
+ * [ContinuationInterceptor] is specified and adds optional support for debugging facilities (when turned on)
+ * and copyable-thread-local facilities on JVM.
  * 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 combined = foldCopies(coroutineContext, context, true)
     val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
     return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
         debug + Dispatchers.Default else debug
 }
 
 /**
+ * Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext].
+ * @suppress
+ */
+@InternalCoroutinesApi
+public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext {
+    /*
+     * Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements)
+     * contains copyable elements.
+     */
+    if (!addedContext.hasCopyableElements()) return this + addedContext
+    return foldCopies(this, addedContext, false)
+}
+
+private fun CoroutineContext.hasCopyableElements(): Boolean =
+    fold(false) { result, it -> result || it is CopyableThreadContextElement<*> }
+
+/**
+ * Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary.
+ * The rules are the following:
+ * * If neither context has CTCE, the sum of two contexts is returned
+ * * Every CTCE from the left-hand side context that does not have a matching (by key) element from right-hand side context
+ *   is [copied][CopyableThreadContextElement.copyForChild] if [isNewCoroutine] is `true`.
+ * * Every CTCE from the left-hand side context that has a matching element in the right-hand side context is [merged][CopyableThreadContextElement.mergeForChild]
+ * * Every CTCE from the right-hand side context that hasn't been merged is copied
+ * * Everything else is added to the resulting context as is.
+ */
+private fun foldCopies(originalContext: CoroutineContext, appendContext: CoroutineContext, isNewCoroutine: Boolean): CoroutineContext {
+    // Do we have something to copy left-hand side?
+    val hasElementsLeft = originalContext.hasCopyableElements()
+    val hasElementsRight = appendContext.hasCopyableElements()
+
+    // Nothing to fold, so just return the sum of contexts
+    if (!hasElementsLeft && !hasElementsRight) {
+        return originalContext + appendContext
+    }
+
+    var leftoverContext = appendContext
+    val folded = originalContext.fold<CoroutineContext>(EmptyCoroutineContext) { result, element ->
+        if (element !is CopyableThreadContextElement<*>) return@fold result + element
+        // Will this element be overwritten?
+        val newElement = leftoverContext[element.key]
+        // No, just copy it
+        if (newElement == null) {
+            // For 'withContext'-like builders we do not copy as the element is not shared
+            return@fold result + if (isNewCoroutine) element.copyForChild() else element
+        }
+        // Yes, then first remove the element from append context
+        leftoverContext = leftoverContext.minusKey(element.key)
+        // Return the sum
+        @Suppress("UNCHECKED_CAST")
+        return@fold result + (element as CopyableThreadContextElement<Any?>).mergeForChild(newElement)
+    }
+
+    if (hasElementsRight) {
+        leftoverContext = leftoverContext.fold<CoroutineContext>(EmptyCoroutineContext) { result, element ->
+            // We're appending new context element -- we have to copy it, otherwise it may be shared with others
+            if (element is CopyableThreadContextElement<*>) {
+                return@fold result + element.copyForChild()
+            }
+            return@fold result + element
+        }
+    }
+    return folded + leftoverContext
+}
+
+/**
  * Executes a block using a given coroutine context.
  */
 internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T {
@@ -72,7 +124,7 @@
 internal fun Continuation<*>.updateUndispatchedCompletion(context: CoroutineContext, oldValue: Any?): UndispatchedCoroutine<*>? {
     if (this !is CoroutineStackFrame) return null
     /*
-     * Fast-path to detect whether we have unispatched coroutine at all in our stack.
+     * Fast-path to detect whether we have undispatched coroutine at all in our stack.
      *
      * Implementation note.
      * If we ever find that stackwalking for thread-locals is way too slow, here is another idea:
@@ -83,8 +135,8 @@
      *    Both options should work, but it requires more careful studying of the performance
      *    and, mostly, maintainability impact.
      */
-    val potentiallyHasUndispatchedCorotuine = context[UndispatchedMarker] !== null
-    if (!potentiallyHasUndispatchedCorotuine) return null
+    val potentiallyHasUndispatchedCoroutine = context[UndispatchedMarker] !== null
+    if (!potentiallyHasUndispatchedCoroutine) return null
     val completion = undispatchedCompletion()
     completion?.saveThreadContext(context, oldValue)
     return completion
@@ -102,7 +154,7 @@
 
 /**
  * Marker indicating that [UndispatchedCoroutine] exists somewhere up in the stack.
- * Used as a performance optimization to avoid stack walking where it is not nesessary.
+ * Used as a performance optimization to avoid stack walking where it is not necessary.
  */
 private object UndispatchedMarker: CoroutineContext.Element, CoroutineContext.Key<UndispatchedMarker> {
     override val key: CoroutineContext.Key<*>
@@ -115,26 +167,65 @@
     uCont: Continuation<T>
 ) : ScopeCoroutine<T>(if (context[UndispatchedMarker] == null) context + UndispatchedMarker else context, uCont) {
 
-    private var savedContext: CoroutineContext? = null
-    private var savedOldValue: Any? = null
+    /*
+     * The state is thread-local because this coroutine can be used concurrently.
+     * Scenario of usage (withContinuationContext):
+     * val state = saveThreadContext(ctx)
+     * try {
+     *     invokeSmthWithThisCoroutineAsCompletion() // Completion implies that 'afterResume' will be called
+     *     // COROUTINE_SUSPENDED is returned
+     * } finally {
+     *     thisCoroutine().clearThreadContext() // Concurrently the "smth" could've been already resumed on a different thread
+     *     // and it also calls saveThreadContext and clearThreadContext
+     * }
+     */
+    private var threadStateToRecover = ThreadLocal<Pair<CoroutineContext, Any?>>()
+
+    init {
+        /*
+         * This is a hack for a very specific case in #2930 unless #3253 is implemented.
+         * 'ThreadLocalStressTest' covers this change properly.
+         *
+         * The scenario this change covers is the following:
+         * 1) The coroutine is being started as plain non kotlinx.coroutines related suspend function,
+         *    e.g. `suspend fun main` or, more importantly, Ktor `SuspendFunGun`, that is invoking
+         *    `withContext(tlElement)` which creates `UndispatchedCoroutine`.
+         * 2) It (original continuation) is then not wrapped into `DispatchedContinuation` via `intercept()`
+         *    and goes neither through `DC.run` nor through `resumeUndispatchedWith` that both
+         *    do thread context element tracking.
+         * 3) So thread locals never got chance to get properly set up via `saveThreadContext`,
+         *    but when `withContext` finishes, it attempts to recover thread locals in its `afterResume`.
+         *
+         * Here we detect precisely this situation and properly setup context to recover later.
+         *
+         */
+        if (uCont.context[ContinuationInterceptor] !is CoroutineDispatcher) {
+            /*
+             * We cannot just "read" the elements as there is no such API,
+             * so we update-restore it immediately and use the intermediate value
+             * as the initial state, leveraging the fact that thread context element
+             * is idempotent and such situations are increasingly rare.
+             */
+            val values = updateThreadContext(context, null)
+            restoreThreadContext(context, values)
+            saveThreadContext(context, values)
+        }
+    }
 
     fun saveThreadContext(context: CoroutineContext, oldValue: Any?) {
-        savedContext = context
-        savedOldValue = oldValue
+        threadStateToRecover.set(context to oldValue)
     }
 
     fun clearThreadContext(): Boolean {
-        if (savedContext == null) return false
-        savedContext = null
-        savedOldValue = null
+        if (threadStateToRecover.get() == null) return false
+        threadStateToRecover.set(null)
         return true
     }
 
     override fun afterResume(state: Any?) {
-        savedContext?.let { context ->
-            restoreThreadContext(context, savedOldValue)
-            savedContext = null
-            savedOldValue = null
+        threadStateToRecover.get()?.let { (ctx, value) ->
+            restoreThreadContext(ctx, value)
+            threadStateToRecover.set(null)
         }
         // resume undispatched -- update context but stay on the same dispatcher
         val result = recoverResult(state, uCont)
@@ -153,6 +244,7 @@
 
 private const val DEBUG_THREAD_NAME_SEPARATOR = " @"
 
+@IgnoreJreRequirement // desugared hashcode implementation
 internal data class CoroutineId(
     val id: Long
 ) : ThreadContextElement<String>, AbstractCoroutineContextElement(CoroutineId) {
diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
index 6d06969..4259092 100644
--- a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
+++ b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
@@ -22,6 +22,25 @@
         CoroutineExceptionHandler::class.java.classLoader
 ).iterator().asSequence().toList()
 
+/**
+ * Private exception without stacktrace that is added to suppressed exceptions of the original exception
+ * when it is reported to the last-ditch current thread 'uncaughtExceptionHandler'.
+ *
+ * The purpose of this exception is to add an otherwise inaccessible diagnostic information and to
+ * be able to poke the failing coroutine context in the debugger.
+ */
+private class DiagnosticCoroutineContextException(private val context: CoroutineContext) : RuntimeException() {
+    override fun getLocalizedMessage(): String {
+        return context.toString()
+    }
+
+    override fun fillInStackTrace(): Throwable {
+        // Prevent Android <= 6.0 bug, #1866
+        stackTrace = emptyArray()
+        return this
+    }
+}
+
 internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
     // use additional extension handlers
     for (handler in handlers) {
@@ -36,5 +55,8 @@
 
     // use thread's handler
     val currentThread = Thread.currentThread()
+    // addSuppressed is never user-defined and cannot normally throw with the only exception being OOM
+    // we do ignore that just in case to definitely deliver the exception
+    runCatching { exception.addSuppressed(DiagnosticCoroutineContextException(context)) }
     currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
 }
diff --git a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
index fe02027..c993bc2 100644
--- a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
+++ b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
@@ -4,10 +4,25 @@
 
 package kotlinx.coroutines
 
+import kotlinx.coroutines.internal.*
 import java.util.concurrent.*
 import kotlin.coroutines.*
 
-internal actual val DefaultDelay: Delay = DefaultExecutor
+private val defaultMainDelayOptIn = systemProp("kotlinx.coroutines.main.delay", false)
+
+internal actual val DefaultDelay: Delay = initializeDefaultDelay()
+
+private fun initializeDefaultDelay(): Delay {
+    // Opt-out flag
+    if (!defaultMainDelayOptIn) return DefaultExecutor
+    val main = Dispatchers.Main
+    /*
+     * When we already are working with UI and Main threads, it makes
+     * no sense to create a separate thread with timer that cannot be controller
+     * by the UI runtime.
+     */
+    return if (main.isMissing() || main !is Delay) DefaultExecutor else main
+}
 
 @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
 internal actual object DefaultExecutor : EventLoopImplBase(), Runnable {
@@ -17,13 +32,13 @@
         incrementUseCount() // this event loop is never completed
     }
 
-    private const val DEFAULT_KEEP_ALIVE = 1000L // in milliseconds
+    private const val DEFAULT_KEEP_ALIVE_MS = 1000L // in milliseconds
 
     private val KEEP_ALIVE_NANOS = TimeUnit.MILLISECONDS.toNanos(
         try {
-            java.lang.Long.getLong("kotlinx.coroutines.DefaultExecutor.keepAlive", DEFAULT_KEEP_ALIVE)
+            java.lang.Long.getLong("kotlinx.coroutines.DefaultExecutor.keepAlive", DEFAULT_KEEP_ALIVE_MS)
         } catch (e: SecurityException) {
-            DEFAULT_KEEP_ALIVE
+            DEFAULT_KEEP_ALIVE_MS
         })
 
     @Suppress("ObjectPropertyName")
@@ -37,15 +52,39 @@
     private const val ACTIVE = 1
     private const val SHUTDOWN_REQ = 2
     private const val SHUTDOWN_ACK = 3
+    private const val SHUTDOWN = 4
 
     @Volatile
     private var debugStatus: Int = FRESH
 
+    private val isShutDown: Boolean get() = debugStatus == SHUTDOWN
+
     private val isShutdownRequested: Boolean get() {
         val debugStatus = debugStatus
         return debugStatus == SHUTDOWN_REQ || debugStatus == SHUTDOWN_ACK
     }
 
+    actual override fun enqueue(task: Runnable) {
+        if (isShutDown) shutdownError()
+        super.enqueue(task)
+    }
+
+     override fun reschedule(now: Long, delayedTask: DelayedTask) {
+         // Reschedule on default executor can only be invoked after Dispatchers.shutdown
+         shutdownError()
+    }
+
+    private fun shutdownError() {
+        throw RejectedExecutionException("DefaultExecutor was shut down. " +
+            "This error indicates that Dispatchers.shutdown() was invoked prior to completion of exiting coroutines, leaving coroutines in incomplete state. " +
+            "Please refer to Dispatchers.shutdown documentation for more details")
+    }
+
+    override fun shutdown() {
+        debugStatus = SHUTDOWN
+        super.shutdown()
+    }
+
     /**
      * All event loops are using DefaultExecutor#invokeOnTimeout to avoid livelock on
      * ```
@@ -118,9 +157,8 @@
         return true
     }
 
-    // used for tests
-    @Synchronized
-    fun shutdown(timeout: Long) {
+    @Synchronized // used _only_ for tests
+    fun shutdownForTests(timeout: Long) {
         val deadline = System.currentTimeMillis() + timeout
         if (!isShutdownRequested) debugStatus = SHUTDOWN_REQ
         // loop while there is anything to do immediately or deadline passes
diff --git a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
index d82598e..251a567 100644
--- a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
+++ b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
@@ -21,7 +21,7 @@
 public actual object Dispatchers {
     /**
      * The default [CoroutineDispatcher] that is used by all standard builders like
-     * [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc
+     * [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
@@ -29,7 +29,7 @@
      * 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()
+    public actual val Default: CoroutineDispatcher = DefaultScheduler
 
     /**
      * A coroutine dispatcher that is confined to the Main thread operating with UI objects.
@@ -86,7 +86,7 @@
      * 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
+     * [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to
      * the value of [CoroutineStart.UNDISPATCHED].
      */
     @JvmStatic
@@ -100,20 +100,64 @@
      * "`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).
      *
-     * Moreover, the maximum configurable number of threads is capped by the
-     * `kotlinx.coroutines.scheduler.max.pool.size` system property.
-     * If you need a higher number of parallel threads,
-     * you should use a custom dispatcher backed by your own thread pool.
+     * ### Elasticity for limited parallelism
+     *
+     * `Dispatchers.IO` has a unique property of elasticity: its views
+     * obtained with [CoroutineDispatcher.limitedParallelism] are
+     * not restricted by the `Dispatchers.IO` parallelism. Conceptually, there is
+     * a dispatcher backed by an unlimited pool of threads, and both `Dispatchers.IO`
+     * and views of `Dispatchers.IO` are actually views of that dispatcher. In practice
+     * this means that, despite not abiding by `Dispatchers.IO`'s parallelism
+     * restrictions, its views share threads and resources with it.
+     *
+     * In the following example
+     * ```
+     * // 100 threads for MySQL connection
+     * val myMysqlDbDispatcher = Dispatchers.IO.limitedParallelism(100)
+     * // 60 threads for MongoDB connection
+     * val myMongoDbDispatcher = Dispatchers.IO.limitedParallelism(60)
+     * ```
+     * the system may have up to `64 + 100 + 60` threads dedicated to blocking tasks during peak loads,
+     * but during its steady state there is only a small number of threads shared
+     * among `Dispatchers.IO`, `myMysqlDbDispatcher` and `myMongoDbDispatcher`.
      *
      * ### Implementation note
      *
-     * This dispatcher shares threads with the [Default][Dispatchers.Default] dispatcher, so using
+     * This dispatcher and its views share threads with the [Default][Dispatchers.Default] dispatcher, so using
      * `withContext(Dispatchers.IO) { ... }` when already running on the [Default][Dispatchers.Default]
-     * dispatcher does not lead to an actual switching to another thread &mdash; typically execution
-     * continues in the same thread.
+     * dispatcher typically does not lead to an actual switching to another thread. In such scenarios,
+     * the underlying implementation attempts to keep the execution on the same thread on a best-effort basis.
+     *
      * As a result of thread sharing, more than 64 (default parallelism) threads can be created (but not used)
      * during operations over IO dispatcher.
      */
     @JvmStatic
-    public val IO: CoroutineDispatcher = DefaultScheduler.IO
+    public val IO: CoroutineDispatcher = DefaultIoScheduler
+
+    /**
+     * Shuts down built-in dispatchers, such as [Default] and [IO],
+     * stopping all the threads associated with them and making them reject all new tasks.
+     * Dispatcher used as a fallback for time-related operations (`delay`, `withTimeout`)
+     * and to handle rejected tasks from other dispatchers is also shut down.
+     *
+     * This is a **delicate** API. It is not supposed to be called from a general
+     * application-level code and its invocation is irreversible.
+     * The invocation of shutdown affects most of the coroutines machinery and
+     * leaves the coroutines framework in an inoperable state.
+     * The shutdown method should only be invoked when there are no pending tasks or active coroutines.
+     * Otherwise, the behavior is unspecified: the call to `shutdown` may throw an exception without completing
+     * the shutdown, or it may finish successfully, but the remaining jobs will be in a permanent dormant state,
+     * never completing nor executing.
+     *
+     * The main goal of the shutdown is to stop all background threads associated with the coroutines
+     * framework in order to make kotlinx.coroutines classes unloadable by Java Virtual Machine.
+     * It is only recommended to be used in containerized environments (OSGi, Gradle plugins system,
+     * IDEA plugins) at the end of the container lifecycle.
+     */
+    @DelicateCoroutinesApi
+    public fun shutdown() {
+        DefaultExecutor.shutdown()
+        // Also shuts down Dispatchers.IO
+        DefaultScheduler.shutdown()
+    }
 }
diff --git a/kotlinx-coroutines-core/jvm/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt
index e49c7dc..1ee651a 100644
--- a/kotlinx-coroutines-core/jvm/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/jvm/src/EventLoop.kt
@@ -13,8 +13,7 @@
             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)
+    protected actual open fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) {
         DefaultExecutor.schedule(now, delayedTask)
     }
 }
@@ -47,3 +46,5 @@
 @InternalCoroutinesApi
 public fun processNextEventInCurrentThread(): Long =
     ThreadLocalEventLoop.currentOrNull()?.processNextEvent() ?: Long.MAX_VALUE
+
+internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit) = block()
diff --git a/kotlinx-coroutines-core/jvm/src/Exceptions.kt b/kotlinx-coroutines-core/jvm/src/Exceptions.kt
index 007a0c9..48b4788 100644
--- a/kotlinx-coroutines-core/jvm/src/Exceptions.kt
+++ b/kotlinx-coroutines-core/jvm/src/Exceptions.kt
@@ -29,7 +29,7 @@
 internal actual class JobCancellationException public actual constructor(
     message: String,
     cause: Throwable?,
-    @JvmField internal actual val job: Job
+    @JvmField @Transient internal actual val job: Job
 ) : CancellationException(message), CopyableThrowable<JobCancellationException> {
 
     init {
diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt
index 7ea3cc6..4e98e7b 100644
--- a/kotlinx-coroutines-core/jvm/src/Executors.kt
+++ b/kotlinx-coroutines-core/jvm/src/Executors.kt
@@ -37,6 +37,9 @@
     public abstract override fun close()
 }
 
+@ExperimentalCoroutinesApi
+public actual typealias CloseableCoroutineDispatcher = ExecutorCoroutineDispatcher
+
 /**
  * Converts an instance of [ExecutorService] to an implementation of [ExecutorCoroutineDispatcher].
  *
diff --git a/kotlinx-coroutines-core/jvm/src/Interruptible.kt b/kotlinx-coroutines-core/jvm/src/Interruptible.kt
index b873ead..0bded76 100644
--- a/kotlinx-coroutines-core/jvm/src/Interruptible.kt
+++ b/kotlinx-coroutines-core/jvm/src/Interruptible.kt
@@ -8,7 +8,7 @@
 import kotlin.coroutines.*
 
 /**
- * Calls the specified [block] with a given coroutine context in a interruptible manner.
+ * Calls the specified [block] with a given coroutine context in an interruptible manner.
  * The blocking code block will be interrupted and this function will throw [CancellationException]
  * if the coroutine is cancelled.
  *
@@ -30,6 +30,11 @@
  * suspend fun <T> BlockingQueue<T>.awaitTake(): T =
  *         runInterruptible(Dispatchers.IO) { queue.take() }
  * ```
+ *
+ * `runInterruptible` uses [withContext] as an underlying mechanism for switching context,
+ * meaning that the supplied [block] is invoked in an [undispatched][CoroutineStart.UNDISPATCHED]
+ * manner directly by the caller if [CoroutineDispatcher] from the current [coroutineContext][currentCoroutineContext]
+ * is the same as the one supplied in [context].
  */
 public suspend fun <T> runInterruptible(
     context: CoroutineContext = EmptyCoroutineContext,
diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
index 37fd70a..bfe9995 100644
--- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
+++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
@@ -48,6 +48,15 @@
  * this coroutine suspends.
  *
  * To use [ThreadLocal] variable within the coroutine use [ThreadLocal.asContextElement][asContextElement] function.
+ *
+ * ### Reentrancy and thread-safety
+ *
+ * Correct implementations of this interface must expect that calls to [restoreThreadContext]
+ * may happen in parallel to the subsequent [updateThreadContext] and [restoreThreadContext] operations.
+ * See [CopyableThreadContextElement] for advanced interleaving details.
+ *
+ * All implementations of [ThreadContextElement] should be thread-safe and guard their internal mutable state
+ * within an element accordingly.
  */
 public interface ThreadContextElement<S> : CoroutineContext.Element {
     /**
@@ -78,6 +87,110 @@
 }
 
 /**
+ * A [ThreadContextElement] copied whenever a child coroutine inherits a context containing it.
+ *
+ * When an API uses a _mutable_ [ThreadLocal] for consistency, a [CopyableThreadContextElement]
+ * can give coroutines "coroutine-safe" write access to that `ThreadLocal`.
+ *
+ * A write made to a `ThreadLocal` with a matching [CopyableThreadContextElement] by a coroutine
+ * will be visible to _itself_ and any child coroutine launched _after_ that write.
+ *
+ * Writes will not be visible to the parent coroutine, peer coroutines, or coroutines that happen
+ * to use the same thread. Writes made to the `ThreadLocal` by the parent coroutine _after_
+ * launching a child coroutine will not be visible to that child coroutine.
+ *
+ * This can be used to allow a coroutine to use a mutable ThreadLocal API transparently and
+ * correctly, regardless of the coroutine's structured concurrency.
+ *
+ * This example adapts a `ThreadLocal` method trace to be "coroutine local" while the method trace
+ * is in a coroutine:
+ *
+ * ```
+ * class TraceContextElement(private val traceData: TraceData?) : CopyableThreadContextElement<TraceData?> {
+ *     companion object Key : CoroutineContext.Key<TraceContextElement>
+ *
+ *     override val key: CoroutineContext.Key<TraceContextElement> = Key
+ *
+ *     override fun updateThreadContext(context: CoroutineContext): TraceData? {
+ *         val oldState = traceThreadLocal.get()
+ *         traceThreadLocal.set(traceData)
+ *         return oldState
+ *     }
+ *
+ *     override fun restoreThreadContext(context: CoroutineContext, oldState: TraceData?) {
+ *         traceThreadLocal.set(oldState)
+ *     }
+ *
+ *     override fun copyForChild(): TraceContextElement {
+ *         // Copy from the ThreadLocal source of truth at child coroutine launch time. This makes
+ *         // ThreadLocal writes between resumption of the parent coroutine and the launch of the
+ *         // child coroutine visible to the child.
+ *         return TraceContextElement(traceThreadLocal.get()?.copy())
+ *     }
+ *
+ *     override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
+ *         // Merge operation defines how to handle situations when both
+ *         // the parent coroutine has an element in the context and
+ *         // an element with the same key was also
+ *         // explicitly passed to the child coroutine.
+ *         // If merging does not require special behavior,
+ *         // the copy of the element can be returned.
+ *         return TraceContextElement(traceThreadLocal.get()?.copy())
+ *     }
+ * }
+ * ```
+ *
+ * A coroutine using this mechanism can safely call Java code that assumes the corresponding thread local element's
+ * value is installed into the target thread local.
+ *
+ * ### Reentrancy and thread-safety
+ *
+ * Correct implementations of this interface must expect that calls to [restoreThreadContext]
+ * may happen in parallel to the subsequent [updateThreadContext] and [restoreThreadContext] operations.
+ *
+ * Even though an element is copied for each child coroutine, an implementation should be able to handle the following
+ * interleaving when a coroutine with the corresponding element is launched on a multithreaded dispatcher:
+ *
+ * ```
+ * coroutine.updateThreadContext() // Thread #1
+ * ... coroutine body ...
+ * // suspension + immediate dispatch happen here
+ * coroutine.updateThreadContext() // Thread #2, coroutine is already resumed
+ * // ... coroutine body after suspension point on Thread #2 ...
+ * coroutine.restoreThreadContext() // Thread #1, is invoked late because Thread #1 is slow
+ * coroutine.restoreThreadContext() // Thread #2, may happen in parallel with the previous restore
+ * ```
+ *
+ * All implementations of [CopyableThreadContextElement] should be thread-safe and guard their internal mutable state
+ * within an element accordingly.
+ */
+@DelicateCoroutinesApi
+@ExperimentalCoroutinesApi
+public interface CopyableThreadContextElement<S> : ThreadContextElement<S> {
+
+    /**
+     * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child
+     * coroutine's context that is under construction if the added context does not contain an element with the same [key].
+     *
+     * This function is called on the element each time a new coroutine inherits a context containing it,
+     * and the returned value is folded into the context given to the child.
+     *
+     * Since this method is called whenever a new coroutine is launched in a context containing this
+     * [CopyableThreadContextElement], implementations are performance-sensitive.
+     */
+    public fun copyForChild(): CopyableThreadContextElement<S>
+
+    /**
+     * Returns a [CopyableThreadContextElement] to replace `this` `CopyableThreadContextElement` in the child
+     * coroutine's context that is under construction if the added context does contain an element with the same [key].
+     *
+     * This method is invoked on the original element, accepting as the parameter
+     * the element that is supposed to overwrite it.
+     */
+    public fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext
+}
+
+/**
  * 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.
diff --git a/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt
index 99e3b46..dc0b7e2 100644
--- a/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt
+++ b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt
@@ -17,11 +17,12 @@
  * then the [Job] of the affected task is [cancelled][Job.cancel] and the task is submitted to the
  * [Dispatchers.IO], so that the affected coroutine can cleanup its resources and promptly complete.
  *
- * **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.
+ * This is a **delicate** API. The result of this method is a closeable resource with the
+ * associated native resources (threads). It should not be allocated in place,
+ * should be closed at the end of its lifecycle, and has non-trivial memory and CPU footprint.
+ * If you do not need a separate thread-pool, but only have to limit effective parallelism of the dispatcher,
+ * it is recommended to use [CoroutineDispatcher.limitedParallelism] instead.
+ *
  * 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()`.
@@ -29,8 +30,8 @@
  *
  * @param name the base name of the created thread.
  */
-@ObsoleteCoroutinesApi
-public fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher =
+@DelicateCoroutinesApi
+public actual fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher =
     newFixedThreadPoolContext(1, name)
 
 /**
@@ -43,11 +44,12 @@
  * then the [Job] of the affected task is [cancelled][Job.cancel] and the task is submitted to the
  * [Dispatchers.IO], so that the affected coroutine can cleanup its resources and promptly complete.
  *
- * **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.
+ * This is a **delicate** API. The result of this method is a closeable resource with the
+ * associated native resources (threads). It should not be allocated in place,
+ * should be closed at the end of its lifecycle, and has non-trivial memory and CPU footprint.
+ * If you do not need a separate thread-pool, but only have to limit effective parallelism of the dispatcher,
+ * it is recommended to use [CoroutineDispatcher.limitedParallelism] instead.
+ *
  * 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()`.
@@ -56,8 +58,8 @@
  * @param nThreads the number of threads.
  * @param name the base name of the created threads.
  */
-@ObsoleteCoroutinesApi
-public fun newFixedThreadPoolContext(nThreads: Int, name: String): ExecutorCoroutineDispatcher {
+@DelicateCoroutinesApi
+public actual fun newFixedThreadPoolContext(nThreads: Int, name: String): ExecutorCoroutineDispatcher {
     require(nThreads >= 1) { "Expected at least one thread, but $nThreads specified" }
     val threadNo = AtomicInteger()
     val executor = Executors.newScheduledThreadPool(nThreads) { runnable ->
diff --git a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
index 4657bc7..748f528 100644
--- a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
+++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
@@ -163,7 +163,7 @@
         return super.send(element)
     }
 
-    @Suppress("DEPRECATION")
+    @Suppress("DEPRECATION", "DEPRECATION_ERROR")
     override fun offer(element: E): Boolean {
         start()
         return super.offer(element)
diff --git a/kotlinx-coroutines-core/jvm/src/channels/Channels.kt b/kotlinx-coroutines-core/jvm/src/channels/Channels.kt
deleted file mode 100644
index 0df8278..0000000
--- a/kotlinx-coroutines-core/jvm/src/channels/Channels.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-@file:JvmMultifileClass
-@file:JvmName("ChannelsKt")
-
-package kotlinx.coroutines.channels
-
-import kotlinx.coroutines.*
-
-/**
- * **Deprecated** blocking variant of send.
- * This method is deprecated in the favour of [trySendBlocking].
- *
- * `sendBlocking` is a dangerous primitive &mdash; it throws an exception
- * if the channel was closed or, more commonly, cancelled.
- * Cancellation exceptions in non-blocking code are unexpected and frequently
- * trigger internal failures.
- *
- * These bugs are hard-to-spot during code review and they forced users to write
- * their own wrappers around `sendBlocking`.
- * So this function is deprecated and replaced with a more explicit primitive.
- *
- * The real-world example of broken usage with Firebase:
- *
- * ```kotlin
- * callbackFlow {
- *     val listener = object : ValueEventListener {
- *         override fun onDataChange(snapshot: DataSnapshot) {
- *             // This line may fail and crash the app when the downstream flow is cancelled
- *             sendBlocking(DataSnapshot(snapshot))
- *         }
- *
- *         override fun onCancelled(error: DatabaseError) {
- *             close(error.toException())
- *         }
- *     }
- *
- *     firebaseQuery.addValueEventListener(listener)
- *     awaitClose { firebaseQuery.removeEventListener(listener) }
- * }
- * ```
- */
-@Deprecated(
-    level = DeprecationLevel.WARNING,
-    message = "Deprecated in the favour of 'trySendBlocking'. " +
-        "Consider handling the result of 'trySendBlocking' explicitly and rethrow exception if necessary",
-    replaceWith = ReplaceWith("trySendBlocking(element)")
-)
-public fun <E> SendChannel<E>.sendBlocking(element: E) {
-    // fast path
-    if (trySend(element).isSuccess)
-        return
-    // slow path
-    runBlocking {
-        send(element)
-    }
-}
-
-/**
- * Adds [element] into to this channel, **blocking** the caller while this channel is full,
- * and returning either [successful][ChannelResult.isSuccess] result when the element was added, or
- * failed result representing closed channel with a corresponding exception.
- *
- * This is a way to call [Channel.send] method in a safe manner inside a blocking code using [runBlocking] and catching,
- * so this function should not be used from coroutine.
- *
- * Example of usage:
- *
- * ```
- * // From callback API
- * channel.trySendBlocking(element)
- *     .onSuccess { /* request next element or debug log */ }
- *     .onFailure { t: Throwable? -> /* throw or log */ }
- * ```
- *
- * For this operation it is guaranteed that [failure][ChannelResult.failed] always contains an exception in it.
- *
- * @throws [InterruptedException] if the current thread is interrupted during the blocking send operation.
- */
-@Throws(InterruptedException::class)
-public fun <E> SendChannel<E>.trySendBlocking(element: E): ChannelResult<Unit> {
-    /*
-     * Sent successfully -- bail out.
-     * But failure may indicate either that the channel it full or that
-     * it is close. Go to slow path on failure to simplify the successful path and
-     * to materialize default exception.
-     */
-    trySend(element).onSuccess { return ChannelResult.success(Unit) }
-    return runBlocking {
-        val r = runCatching { send(element) }
-        if (r.isSuccess) ChannelResult.success(Unit)
-        else ChannelResult.closed(r.exceptionOrNull())
-    }
-}
diff --git a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt b/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt
index 8ef0c18..4b0ce3f 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt
@@ -4,12 +4,13 @@
 
 package kotlinx.coroutines.debug
 
-import kotlinx.coroutines.debug.internal.DebugProbesImpl
+import android.annotation.*
+import kotlinx.coroutines.debug.internal.*
+import org.codehaus.mojo.animal_sniffer.*
 import sun.misc.*
 import java.lang.instrument.*
 import java.lang.instrument.ClassFileTransformer
 import java.security.*
-import android.annotation.*
 
 /*
  * This class is loaded if and only if kotlinx-coroutines-core was used as -javaagent argument,
@@ -17,17 +18,16 @@
  */
 @Suppress("unused")
 @SuppressLint("all")
+@IgnoreJRERequirement // Never touched on Android
 internal object AgentPremain {
 
-    public var isInstalledStatically = false
-
     private val enableCreationStackTraces = runCatching {
         System.getProperty("kotlinx.coroutines.debug.enable.creation.stack.trace")?.toBoolean()
     }.getOrNull() ?: DebugProbesImpl.enableCreationStackTraces
 
     @JvmStatic
-    public fun premain(args: String?, instrumentation: Instrumentation) {
-        isInstalledStatically = true
+    fun premain(args: String?, instrumentation: Instrumentation) {
+        AgentInstallationType.isInstalledStatically = true
         instrumentation.addTransformer(DebugProbesTransformer)
         DebugProbesImpl.enableCreationStackTraces = enableCreationStackTraces
         DebugProbesImpl.install()
@@ -52,7 +52,7 @@
              * on the fly (-> get rid of ASM dependency).
              * You can verify its content either by using javap on it or looking at out integration test module.
              */
-            isInstalledStatically = true
+            AgentInstallationType.isInstalledStatically = true
             return loader.getResourceAsStream("DebugProbesKt.bin").readBytes()
         }
     }
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt
new file mode 100644
index 0000000..0e9b26c
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.internal
+
+/**
+ * Object used to differentiate between agent installed statically or dynamically.
+ * This is done in a separate object so [DebugProbesImpl] can check for static installation
+ * without having to depend on [kotlinx.coroutines.debug.AgentPremain], which is not compatible with Android.
+ * Otherwise, access to `AgentPremain.isInstalledStatically` triggers the load of its internal `ClassFileTransformer`
+ * that is not available on Android.
+ */
+internal object AgentInstallationType {
+    internal var isInstalledStatically = false
+}
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt
index ffb9c2d..b02eac6 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt
@@ -10,11 +10,10 @@
 
 // This is very limited implementation, not suitable as a generic map replacement.
 // It has lock-free get and put with synchronized rehash for simplicity (and better CPU usage on contention)
-@OptIn(ExperimentalStdlibApi::class)
 @Suppress("UNCHECKED_CAST")
 internal class ConcurrentWeakMap<K : Any, V: Any>(
     /**
-     * Weak reference queue is needed when a small key is mapped to a large value and we need to promptly release a
+     * Weak reference queue is needed when a small key is mapped to a large value, and we need to promptly release a
      * reference to the value when the key was already disposed.
      */
     weakRefQueue: Boolean = false
@@ -73,7 +72,7 @@
             while (true) {
                 cleanWeakRef(weakRefQueue.remove() as HashedWeakRef<*>)
             }
-        } catch(e: InterruptedException) {
+        } catch (e: InterruptedException) {
             Thread.currentThread().interrupt()
         }
     }
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
index 05befc1..d358d49 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
@@ -6,7 +6,6 @@
 
 import kotlinx.atomicfu.*
 import kotlinx.coroutines.*
-import kotlinx.coroutines.debug.*
 import kotlinx.coroutines.internal.*
 import kotlinx.coroutines.internal.ScopeCoroutine
 import java.io.*
@@ -82,7 +81,7 @@
     public fun install(): Unit = coroutineStateLock.write {
         if (++installations > 1) return
         startWeakRefCleanerThread()
-        if (AgentPremain.isInstalledStatically) return
+        if (AgentInstallationType.isInstalledStatically) return
         dynamicAttach?.invoke(true) // attach
     }
 
@@ -92,7 +91,7 @@
         stopWeakRefCleanerThread()
         capturedCoroutinesMap.clear()
         callerInfoCache.clear()
-        if (AgentPremain.isInstalledStatically) return
+        if (AgentInstallationType.isInstalledStatically) return
         dynamicAttach?.invoke(false) // detach
     }
 
@@ -103,8 +102,10 @@
     }
 
     private fun stopWeakRefCleanerThread() {
-        weakRefCleanerThread?.interrupt()
+        val thread = weakRefCleanerThread ?: return
         weakRefCleanerThread = null
+        thread.interrupt()
+        thread.join()
     }
 
     public fun hierarchyToString(job: Job): String = coroutineStateLock.write {
@@ -149,10 +150,11 @@
      * Private method that dumps coroutines so that different public-facing method can use
      * to produce different result types.
      */
-    private inline fun <R : Any> dumpCoroutinesInfoImpl(create: (CoroutineOwner<*>, CoroutineContext) -> R): List<R> =
+    private inline fun <R : Any> dumpCoroutinesInfoImpl(crossinline create: (CoroutineOwner<*>, CoroutineContext) -> R): List<R> =
         coroutineStateLock.write {
             check(isInstalled) { "Debug probes are not installed" }
             capturedCoroutines
+                .asSequence()
                 // Stable ordering of coroutines by their sequence number
                 .sortedBy { it.info.sequenceNumber }
                 // Leave in the dump only the coroutines that were not collected while we were dumping them
@@ -160,10 +162,87 @@
                     // Fuse map and filter into one operation to save an inline
                     if (owner.isFinished()) null
                     else owner.info.context?.let { context -> create(owner, context) }
-                }
+                }.toList()
         }
 
     /*
+     * This method optimises the number of packages sent by the IDEA debugger
+     * to a client VM to speed up fetching of coroutine information.
+     *
+     * The return value is an array of objects, which consists of four elements:
+     * 1) A string in a JSON format that stores information that is needed to display
+     *    every coroutine in the coroutine panel in the IDEA debugger.
+     * 2) An array of last observed threads.
+     * 3) An array of last observed frames.
+     * 4) An array of DebugCoroutineInfo.
+     *
+     * ### Implementation note
+     * For methods like `dumpCoroutinesInfo` JDWP provides `com.sun.jdi.ObjectReference`
+     * that does a roundtrip to client VM for *each* field or property read.
+     * To avoid that, we serialize most of the critical for UI data into a primitives
+     * to save an exponential number of roundtrips.
+     *
+     * Internal (JVM-public) method used by IDEA debugger as of 1.6.0-RC.
+     */
+    @OptIn(ExperimentalStdlibApi::class)
+    public fun dumpCoroutinesInfoAsJsonAndReferences(): Array<Any> {
+        val coroutinesInfo = dumpCoroutinesInfo()
+        val size = coroutinesInfo.size
+        val lastObservedThreads = ArrayList<Thread?>(size)
+        val lastObservedFrames = ArrayList<CoroutineStackFrame?>(size)
+        val coroutinesInfoAsJson = ArrayList<String>(size)
+        for (info in coroutinesInfo) {
+            val context = info.context
+            val name = context[CoroutineName.Key]?.name?.toStringWithQuotes()
+            val dispatcher = context[CoroutineDispatcher.Key]?.toStringWithQuotes()
+            coroutinesInfoAsJson.add(
+                """
+                {
+                    "name": $name,
+                    "id": ${context[CoroutineId.Key]?.id},
+                    "dispatcher": $dispatcher,
+                    "sequenceNumber": ${info.sequenceNumber},
+                    "state": "${info.state}"
+                } 
+                """.trimIndent()
+            )
+            lastObservedFrames.add(info.lastObservedFrame)
+            lastObservedThreads.add(info.lastObservedThread)
+        }
+
+        return arrayOf(
+            "[${coroutinesInfoAsJson.joinToString()}]",
+            lastObservedThreads.toTypedArray(),
+            lastObservedFrames.toTypedArray(),
+            coroutinesInfo.toTypedArray()
+        )
+    }
+
+    /*
+     * Internal (JVM-public) method used by IDEA debugger as of 1.6.0-RC.
+     */
+    public fun enhanceStackTraceWithThreadDumpAsJson(info: DebugCoroutineInfo): String {
+        val stackTraceElements = enhanceStackTraceWithThreadDump(info, info.lastObservedStackTrace)
+        val stackTraceElementsInfoAsJson = mutableListOf<String>()
+        for (element in stackTraceElements) {
+            stackTraceElementsInfoAsJson.add(
+                """
+                {
+                    "declaringClass": "${element.className}",
+                    "methodName": "${element.methodName}",
+                    "fileName": ${element.fileName?.toStringWithQuotes()},
+                    "lineNumber": ${element.lineNumber}
+                }
+                """.trimIndent()
+            )
+        }
+
+        return "[${stackTraceElementsInfoAsJson.joinToString()}]"
+    }
+
+    private fun Any.toStringWithQuotes() = "\"$this\""
+
+    /*
      * Internal (JVM-public) method used by IDEA debugger as of 1.4-M3.
      */
     public fun dumpCoroutinesInfo(): List<DebugCoroutineInfo> =
diff --git a/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
index d178060..cfe5b69 100644
--- a/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
+++ b/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
@@ -8,7 +8,7 @@
 import kotlinx.coroutines.flow.*
 
 internal actual class AbortFlowException actual constructor(
-    actual val owner: FlowCollector<*>
+    @JvmField @Transient actual val owner: FlowCollector<*>
 ) : CancellationException("Flow was aborted, no more elements needed") {
 
     override fun fillInStackTrace(): Throwable {
diff --git a/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt
index ea97328..cad3f1a 100644
--- a/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt
+++ b/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt
@@ -29,15 +29,22 @@
 
     @JvmField // Note, it is non-capturing lambda, so no extra allocation during init of SafeCollector
     internal actual val collectContextSize = collectContext.fold(0) { count, _ -> count + 1 }
+
+    // Either context of the last emission or wrapper 'DownstreamExceptionContext'
     private var lastEmissionContext: CoroutineContext? = null
+    // Completion if we are currently suspended or within completion body or null otherwise
     private var completion: Continuation<Unit>? = null
 
-    // ContinuationImpl
+    /*
+     * This property is accessed in two places:
+     * * ContinuationImpl invokes this in its `releaseIntercepted` as `context[ContinuationInterceptor]!!`
+     * * When we are within a callee, it is used to create its continuation object with this collector as completion
+     */
     override val context: CoroutineContext
-        get() = completion?.context ?: EmptyCoroutineContext
+        get() = lastEmissionContext ?: EmptyCoroutineContext
 
     override fun invokeSuspend(result: Result<Any?>): Any {
-        result.onFailure { lastEmissionContext = DownstreamExceptionElement(it) }
+        result.onFailure { lastEmissionContext = DownstreamExceptionContext(it, context) }
         completion?.resumeWith(result as Result<Unit>)
         return COROUTINE_SUSPENDED
     }
@@ -59,7 +66,9 @@
                 emit(uCont, value)
             } catch (e: Throwable) {
                 // Save the fact that exception from emit (or even check context) has been thrown
-                lastEmissionContext = DownstreamExceptionElement(e)
+                // Note, that this can the first emit and lastEmissionContext may not be saved yet,
+                // hence we use `uCont.context` here.
+                lastEmissionContext = DownstreamExceptionContext(e, uCont.context)
                 throw e
             }
         }
@@ -72,9 +81,18 @@
         val previousContext = lastEmissionContext
         if (previousContext !== currentContext) {
             checkContext(currentContext, previousContext, value)
+            lastEmissionContext = currentContext
         }
         completion = uCont
-        return emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
+        val result = emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
+        /*
+         * If the callee hasn't suspended, that means that it won't (it's forbidden) call 'resumeWith` (-> `invokeSuspend`)
+         * and we don't have to retain a strong reference to it to avoid memory leaks.
+         */
+        if (result != COROUTINE_SUSPENDED) {
+            completion = null
+        }
+        return result
     }
 
     private fun checkContext(
@@ -82,14 +100,13 @@
         previousContext: CoroutineContext?,
         value: T
     ) {
-        if (previousContext is DownstreamExceptionElement) {
+        if (previousContext is DownstreamExceptionContext) {
             exceptionTransparencyViolated(previousContext, value)
         }
         checkContext(currentContext)
-        lastEmissionContext = currentContext
     }
 
-    private fun exceptionTransparencyViolated(exception: DownstreamExceptionElement, value: Any?) {
+    private fun exceptionTransparencyViolated(exception: DownstreamExceptionContext, value: Any?) {
         /*
          * Exception transparency ensures that if a `collect` block or any intermediate operator
          * throws an exception, then no more values will be received by it.
@@ -122,14 +139,12 @@
                 For a more detailed explanation, please refer to Flow documentation.
             """.trimIndent())
     }
-
 }
 
-internal class DownstreamExceptionElement(@JvmField val e: Throwable) : CoroutineContext.Element {
-    companion object Key : CoroutineContext.Key<DownstreamExceptionElement>
-
-    override val key: CoroutineContext.Key<*> = Key
-}
+internal class DownstreamExceptionContext(
+    @JvmField val e: Throwable,
+    originalContext: CoroutineContext
+) : CoroutineContext by originalContext
 
 private object NoOpContinuation : Continuation<Any?> {
     override val context: CoroutineContext = EmptyCoroutineContext
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstructor.kt b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstructor.kt
new file mode 100644
index 0000000..a4b321d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstructor.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import android.annotation.SuppressLint
+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 typealias Ctor = (Throwable) -> Throwable?
+
+private val ctorCache = try {
+    if (ANDROID_DETECTED) WeakMapCtorCache
+    else ClassValueCtorCache
+} catch (e: Throwable) {
+    // Fallback on Java 6 or exotic setups
+    WeakMapCtorCache
+}
+
+@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()
+    }
+    return ctorCache.get(exception.javaClass).invoke(exception) as E?
+}
+
+private fun <E : Throwable> createConstructor(clz: Class<E>): Ctor {
+    val nullResult: Ctor = { null } // Pre-cache class
+    // Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
+    if (throwableFields != clz.fieldsCountOrDefault(0)) return nullResult
+    /*
+    * 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.
+    */
+    val constructors = clz.constructors.sortedByDescending { it.parameterTypes.size }
+    for (constructor in constructors) {
+        val result = createSafeConstructor(constructor)
+        if (result != null) return result
+    }
+    return nullResult
+}
+
+private fun createSafeConstructor(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)
+}
+
+internal abstract class CtorCache {
+    abstract fun get(key: Class<out Throwable>): Ctor
+}
+
+private object WeakMapCtorCache : CtorCache() {
+    private val cacheLock = ReentrantReadWriteLock()
+    private val exceptionCtors: WeakHashMap<Class<out Throwable>, Ctor> = WeakHashMap()
+
+    override fun get(key: Class<out Throwable>): Ctor {
+        cacheLock.read { exceptionCtors[key]?.let { return it } }
+        cacheLock.write {
+            exceptionCtors[key]?.let { return it }
+            return createConstructor(key).also { exceptionCtors[key] = it }
+        }
+    }
+}
+
+@IgnoreJreRequirement
+@SuppressLint("NewApi")
+private object ClassValueCtorCache : CtorCache() {
+    private val cache = object : ClassValue<Ctor>() {
+        override fun computeValue(type: Class<*>?): Ctor {
+            @Suppress("UNCHECKED_CAST")
+            return createConstructor(type as Class<out Throwable>)
+        }
+    }
+
+    override fun get(key: Class<out Throwable>): Ctor = cache.get(key)
+}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt
deleted file mode 100644
index 60328eb..0000000
--- a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-import kotlinx.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/InternalAnnotations.kt b/kotlinx-coroutines-core/jvm/src/internal/InternalAnnotations.kt
new file mode 100644
index 0000000..41707f7
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/InternalAnnotations.kt
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2016-2021 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") // Not the same name to WA the bug in the compiler
+internal actual typealias IgnoreJreRequirement = org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
diff --git a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt
index 2d44741..e87952b 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt
@@ -61,7 +61,9 @@
 
 /** @suppress */
 @InternalCoroutinesApi
-public fun MainCoroutineDispatcher.isMissing(): Boolean = this is MissingMainCoroutineDispatcher
+public fun MainCoroutineDispatcher.isMissing(): Boolean =
+    // not checking `this`, as it may be wrapped in a `TestMainDispatcher`, whereas `immediate` never is.
+    this.immediate is MissingMainCoroutineDispatcher
 
 // R8 optimization hook, not const on purpose to enable R8 optimizations via "assumenosideeffects"
 @Suppress("MayBeConstant")
@@ -93,6 +95,9 @@
     override fun isDispatchNeeded(context: CoroutineContext): Boolean =
         missing()
 
+    override fun limitedParallelism(parallelism: Int): CoroutineDispatcher =
+        missing()
+
     override suspend fun delay(time: Long) =
         missing()
 
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt b/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt
new file mode 100644
index 0000000..f949d9f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import java.util.concurrent.atomic.*
+
+/**
+ * Atomic array with lock-free reads and synchronized modifications. It logically has an unbounded size,
+ * is implicitly filled with nulls, and is resized on updates as needed to grow.
+ */
+internal class ResizableAtomicArray<T>(initialLength: Int) {
+    @Volatile
+    private var array = AtomicReferenceArray<T>(initialLength)
+
+    // for debug output
+    public fun currentLength(): Int = array.length()
+
+    public operator fun get(index: Int): T? {
+        val array = this.array // volatile read
+        return if (index < array.length()) array[index] else null
+    }
+
+    // Must not be called concurrently, e.g. always use synchronized(this) to call this function
+    fun setSynchronized(index: Int, value: T?) {
+        val curArray = this.array
+        val curLen = curArray.length()
+        if (index < curLen) {
+            curArray[index] = value
+        } else {
+            val newArray = AtomicReferenceArray<T>((index + 1).coerceAtLeast(2 * curLen))
+            for (i in 0 until curLen) newArray[i] = curArray[i]
+            newArray[index] = value
+            array = newArray // copy done
+        }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
index 84d9d9f..e08d1ce 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
@@ -9,7 +9,6 @@
 import kotlinx.coroutines.internal.*
 import java.io.*
 import java.util.concurrent.*
-import java.util.concurrent.atomic.*
 import java.util.concurrent.locks.*
 import kotlin.math.*
 import kotlin.random.*
@@ -261,7 +260,7 @@
      * works properly
      */
     @JvmField
-    val workers = AtomicReferenceArray<Worker?>(maxPoolSize + 1)
+    val workers = ResizableAtomicArray<Worker>(corePoolSize + 1)
 
     /**
      * Long describing state of workers in this pool.
@@ -480,7 +479,7 @@
              * 3) Only then start the worker, otherwise it may miss its own creation
              */
             val worker = Worker(newIndex)
-            workers[newIndex] = worker
+            workers.setSynchronized(newIndex, worker)
             require(newIndex == incrementCreatedWorkers())
             worker.start()
             return cpuWorkers + 1
@@ -525,7 +524,7 @@
         var dormant = 0
         var terminated = 0
         val queueSizes = arrayListOf<String>()
-        for (index in 1 until workers.length()) {
+        for (index in 1 until workers.currentLength()) {
             val worker = workers[index] ?: continue
             val queueSize = worker.localQueue.size
             when (worker.state) {
@@ -684,6 +683,7 @@
                  * No tasks were found:
                  * 1) Either at least one of the workers has stealable task in its FIFO-buffer with a stealing deadline.
                  *    Then its deadline is stored in [minDelayUntilStealableTask]
+                 * // '2)' can be found below
                  *
                  * Then just park for that duration (ditto re-scanning).
                  * While it could potentially lead to short (up to WORK_STEALING_TIME_RESOLUTION_NS ns) starvations,
@@ -838,7 +838,7 @@
                 val lastIndex = decrementCreatedWorkers()
                 if (lastIndex != oldIndex) {
                     val lastWorker = workers[lastIndex]!!
-                    workers[oldIndex] = lastWorker
+                    workers.setSynchronized(oldIndex, lastWorker)
                     lastWorker.indexInArray = oldIndex
                     /*
                      * Now lastWorker is available at both indices in the array, but it can
@@ -852,7 +852,7 @@
                 /*
                  * 5) It is safe to clear reference from workers array now.
                  */
-                workers[lastIndex] = null
+                workers.setSynchronized(lastIndex, null)
             }
             state = WorkerState.TERMINATED
         }
@@ -968,7 +968,6 @@
  * Checks if the thread is part of a thread pool that supports coroutines.
  * This function is needed for integration with BlockHound.
  */
-@Suppress("UNUSED")
 @JvmName("isSchedulerWorker")
 internal fun isSchedulerWorker(thread: Thread) = thread is CoroutineScheduler.Worker
 
@@ -976,7 +975,6 @@
  * Checks if the thread is running a CPU-bound task.
  * This function is needed for integration with BlockHound.
  */
-@Suppress("UNUSED")
 @JvmName("mayNotBlock")
 internal fun mayNotBlock(thread: Thread) = thread is CoroutineScheduler.Worker &&
     thread.state == CoroutineScheduler.WorkerState.CPU_ACQUIRED
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/Deprecated.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Deprecated.kt
new file mode 100644
index 0000000..e5defba
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/Deprecated.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused")
+
+package kotlinx.coroutines.scheduling
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+/**
+ * This API was "public @InternalApi" and leaked into Ktor enabled-by-default sources.
+ * Since then, we refactored scheduler sources and its API and decided to get rid of it in
+ * its current shape.
+ *
+ * To preserve backwards compatibility with Ktor 1.x, previous version of the code is
+ * extracted here as is and isolated from the rest of code base, so R8 can get rid of it.
+ *
+ * It should be removed after Ktor 3.0.0 (EOL of Ktor 1.x) around 2022.
+ */
+@PublishedApi
+internal open class ExperimentalCoroutineDispatcher(
+    private val corePoolSize: Int,
+    private val maxPoolSize: Int,
+    private val idleWorkerKeepAliveNs: Long,
+    private val schedulerName: String = "CoroutineScheduler"
+) : ExecutorCoroutineDispatcher() {
+    public 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)
+    public 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) {
+            // CoroutineScheduler only rejects execution when it is being closed and this behavior is reserved
+            // for testing purposes, so we don't have to worry about cancelling the affected Job here.
+            DefaultExecutor.dispatch(context, block)
+        }
+
+    override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit =
+        try {
+            coroutineScheduler.dispatch(block, tailDispatch = true)
+        } catch (e: RejectedExecutionException) {
+            // CoroutineScheduler only rejects execution when it is being closed and this behavior is reserved
+            // for testing purposes, so we don't have to worry about cancelling the affected Job here.
+            DefaultExecutor.dispatchYield(context, block)
+        }
+
+    override fun close(): Unit = 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.
+     */
+    fun blocking(parallelism: Int = 16): CoroutineDispatcher {
+        require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
+        return LimitingDispatcher(this, parallelism, null, TASK_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.
+     */
+    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, null, TASK_NON_BLOCKING)
+    }
+
+    internal fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
+        try {
+            coroutineScheduler.dispatch(block, context, tailDispatch)
+        } catch (e: RejectedExecutionException) {
+            // CoroutineScheduler only rejects execution when it is being closed and this behavior is reserved
+            // for testing purposes, so we don't have to worry about cancelling the affected Job here.
+            // TaskContext 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)
+}
+
+private class LimitingDispatcher(
+    private val dispatcher: ExperimentalCoroutineDispatcher,
+    private val parallelism: Int,
+    private val name: String?,
+    override val taskMode: Int
+) : 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, tailDispatch: 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, tailDispatch)
+                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 dispatchYield(context: CoroutineContext, block: Runnable) {
+        dispatch(block, tailDispatch = true)
+    }
+
+    override fun toString(): String {
+        return name ?: "${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/Dispatcher.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
index 7227b07..d55edec 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
@@ -4,56 +4,86 @@
 
 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: CoroutineDispatcher = LimitingDispatcher(
-        this,
-        systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)),
-        "Dispatchers.IO",
-        TASK_PROBABLY_BLOCKING
-    )
-
-    override fun close() {
-        throw UnsupportedOperationException("$DEFAULT_DISPATCHER_NAME cannot be closed")
+// Instance of Dispatchers.Default
+internal object DefaultScheduler : SchedulerCoroutineDispatcher(
+    CORE_POOL_SIZE, MAX_POOL_SIZE,
+    IDLE_WORKER_KEEP_ALIVE_NS, DEFAULT_SCHEDULER_NAME
+) {
+    // Shuts down the dispatcher, used only by Dispatchers.shutdown()
+    internal fun shutdown() {
+        super.close()
     }
 
-    override fun toString(): String = DEFAULT_DISPATCHER_NAME
+    // Overridden in case anyone writes (Dispatchers.Default as ExecutorCoroutineDispatcher).close()
+    override fun close() {
+        throw UnsupportedOperationException("Dispatchers.Default cannot be closed")
+    }
 
-    @InternalCoroutinesApi
-    @Suppress("UNUSED")
-    public fun toDebugString(): String = super.toString()
+    override fun toString(): String = "Dispatchers.Default"
 }
 
-/**
- * @suppress **This is unstable API and it is subject to change.**
- */
-// TODO make internal (and rename) after complete integration
-@InternalCoroutinesApi
-public open class ExperimentalCoroutineDispatcher(
-    private val corePoolSize: Int,
-    private val maxPoolSize: Int,
-    private val idleWorkerKeepAliveNs: Long,
-    private val schedulerName: String = "CoroutineScheduler"
-) : ExecutorCoroutineDispatcher() {
-    public 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)
+// The unlimited instance of Dispatchers.IO that utilizes all the threads CoroutineScheduler provides
+private object UnlimitedIoScheduler : CoroutineDispatcher() {
 
-    @Deprecated(message = "Binary compatibility for Ktor 1.0-beta", level = DeprecationLevel.HIDDEN)
-    public constructor(
-        corePoolSize: Int = CORE_POOL_SIZE,
-        maxPoolSize: Int = MAX_POOL_SIZE
-    ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS)
+    @InternalCoroutinesApi
+    override fun dispatchYield(context: CoroutineContext, block: Runnable) {
+        DefaultScheduler.dispatchWithContext(block, BlockingContext, true)
+    }
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        DefaultScheduler.dispatchWithContext(block, BlockingContext, false)
+    }
+}
+
+// Dispatchers.IO
+internal object DefaultIoScheduler : ExecutorCoroutineDispatcher(), Executor {
+
+    private val default = UnlimitedIoScheduler.limitedParallelism(
+        systemProp(
+            IO_PARALLELISM_PROPERTY_NAME,
+            64.coerceAtLeast(AVAILABLE_PROCESSORS)
+        )
+    )
+
+    override val executor: Executor
+        get() = this
+
+    override fun execute(command: java.lang.Runnable) = dispatch(EmptyCoroutineContext, command)
+
+    @ExperimentalCoroutinesApi
+    override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+        // See documentation to Dispatchers.IO for the rationale
+        return UnlimitedIoScheduler.limitedParallelism(parallelism)
+    }
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        default.dispatch(context, block)
+    }
+
+    @InternalCoroutinesApi
+    override fun dispatchYield(context: CoroutineContext, block: Runnable) {
+        default.dispatchYield(context, block)
+    }
+
+    override fun close() {
+        error("Cannot be invoked on Dispatchers.IO")
+    }
+
+    override fun toString(): String = "Dispatchers.IO"
+}
+
+// Instantiated in tests so we can test it in isolation
+internal open class SchedulerCoroutineDispatcher(
+    private val corePoolSize: Int = CORE_POOL_SIZE,
+    private val maxPoolSize: Int = MAX_POOL_SIZE,
+    private val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
+    private val schedulerName: String = "CoroutineScheduler",
+) : ExecutorCoroutineDispatcher() {
 
     override val executor: Executor
         get() = coroutineScheduler
@@ -61,67 +91,21 @@
     // 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) {
-            // CoroutineScheduler only rejects execution when it is being closed and this behavior is reserved
-            // for testing purposes, so we don't have to worry about cancelling the affected Job here.
-            DefaultExecutor.dispatch(context, block)
-        }
+    private fun createScheduler() =
+        CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
+
+    override fun dispatch(context: CoroutineContext, block: Runnable): Unit = coroutineScheduler.dispatch(block)
 
     override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit =
-        try {
-            coroutineScheduler.dispatch(block, tailDispatch = true)
-        } catch (e: RejectedExecutionException) {
-            // CoroutineScheduler only rejects execution when it is being closed and this behavior is reserved
-            // for testing purposes, so we don't have to worry about cancelling the affected Job here.
-            DefaultExecutor.dispatchYield(context, block)
-        }
-
-    override fun close(): Unit = 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, null, TASK_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, null, TASK_NON_BLOCKING)
-    }
+        coroutineScheduler.dispatch(block, tailDispatch = true)
 
     internal fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
-        try {
-            coroutineScheduler.dispatch(block, context, tailDispatch)
-        } catch (e: RejectedExecutionException) {
-            // CoroutineScheduler only rejects execution when it is being closed and this behavior is reserved
-            // for testing purposes, so we don't have to worry about cancelling the affected Job here.
-            // TaskContext shouldn't be lost here to properly invoke before/after task
-            DefaultExecutor.enqueue(coroutineScheduler.createTask(block, context))
-        }
+        coroutineScheduler.dispatch(block, context, tailDispatch)
     }
 
-    private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
+    override fun close() {
+        coroutineScheduler.close()
+    }
 
     // fot tests only
     @Synchronized
@@ -139,106 +123,3 @@
     // for tests only
     internal fun restore() = usePrivateScheduler() // recreate scheduler
 }
-
-private class LimitingDispatcher(
-    private val dispatcher: ExperimentalCoroutineDispatcher,
-    private val parallelism: Int,
-    private val name: String?,
-    override val taskMode: Int
-) : 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, tailDispatch: 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, tailDispatch)
-                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 dispatchYield(context: CoroutineContext, block: Runnable) {
-        dispatch(block, tailDispatch = true)
-    }
-
-    override fun toString(): String {
-        return name ?: "${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
index da867c9..5403cfc 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt
@@ -9,10 +9,6 @@
 import java.util.concurrent.*
 
 
-// TODO most of these fields will be moved to 'object ExperimentalDispatcher'
-
-// User-visible name
-internal const val DEFAULT_DISPATCHER_NAME = "Dispatchers.Default"
 // Internal debuggability name + thread name prefixes
 internal const val DEFAULT_SCHEDULER_NAME = "DefaultDispatcher"
 
@@ -22,27 +18,24 @@
     "kotlinx.coroutines.scheduler.resolution.ns", 100000L
 )
 
-@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.
+/**
+ * The maximum number of threads allocated for CPU-bound tasks at the default set of dispatchers.
+ *
+ * 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
+    AVAILABLE_PROCESSORS.coerceAtLeast(2),
     minValue = CoroutineScheduler.MIN_SUPPORTED_POOL_SIZE
 )
 
+/** The maximum number of threads allocated for blocking tasks at the default set of dispatchers. */
 @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
-    ),
+    CoroutineScheduler.MAX_SUPPORTED_POOL_SIZE,
     maxValue = CoroutineScheduler.MAX_SUPPORTED_POOL_SIZE
 )
 
@@ -69,14 +62,18 @@
     fun afterTask()
 }
 
-internal object NonBlockingContext : TaskContext {
-    override val taskMode: Int = TASK_NON_BLOCKING
-
+private class TaskContextImpl(override val taskMode: Int): TaskContext {
     override fun afterTask() {
-       // Nothing for non-blocking context
+        // Nothing for non-blocking context
     }
 }
 
+@JvmField
+internal val NonBlockingContext: TaskContext = TaskContextImpl(TASK_NON_BLOCKING)
+
+@JvmField
+internal val BlockingContext: TaskContext = TaskContextImpl(TASK_PROBABLY_BLOCKING)
+
 internal abstract class Task(
     @JvmField var submissionTime: Long,
     @JvmField var taskContext: TaskContext
diff --git a/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt
deleted file mode 100644
index 8526ca2..0000000
--- a/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright 2016-2021 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)
-public 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): Long=
-        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.
-     */
-    public 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(): Unit = 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, context: CoroutineContext): 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")
-        }
-    }
-}
diff --git a/kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt b/kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt
deleted file mode 100644
index 8f9f855..0000000
--- a/kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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 java.lang.reflect.*
-import java.util.concurrent.*
-import kotlin.test.*
-
-@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/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt
new file mode 100644
index 0000000..b46adda
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlin.random.*
+
+actual fun randomWait() {
+    val n = Random.nextInt(1000)
+    if (n < 500) return // no wait 50% of time
+    repeat(n) {
+        BlackHole.sink *= 3
+    }
+    if (n > 900) Thread.yield()
+}
+
+private object BlackHole {
+    @Volatile
+    var sink = 1
+}
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias SuppressSupportingThrowable = Throwable
+
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+actual fun Throwable.printStackTrace() = printStackTrace()
+
+actual fun currentThreadName(): String = Thread.currentThread().name
diff --git a/kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt b/kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt
deleted file mode 100644
index 12941fb..0000000
--- a/kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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/DispatcherKeyTest.kt b/kotlinx-coroutines-core/jvm/test/DispatcherKeyTest.kt
index e2d8ffa..303e8cc 100644
--- a/kotlinx-coroutines-core/jvm/test/DispatcherKeyTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/DispatcherKeyTest.kt
@@ -8,7 +8,7 @@
 import kotlin.coroutines.*
 import kotlin.test.*
 
-@UseExperimental(ExperimentalStdlibApi::class)
+@OptIn(ExperimentalStdlibApi::class)
 class DispatcherKeyTest : TestBase() {
 
     companion object CustomInterceptor : AbstractCoroutineContextElement(ContinuationInterceptor),
diff --git a/kotlinx-coroutines-core/jvm/test/FieldWalker.kt b/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
index 179b2e5..7b2aaf6 100644
--- a/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
+++ b/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
@@ -9,6 +9,7 @@
 import java.text.*
 import java.util.*
 import java.util.Collections.*
+import java.util.concurrent.*
 import java.util.concurrent.atomic.*
 import java.util.concurrent.locks.*
 import kotlin.test.*
@@ -26,11 +27,11 @@
         // excluded/terminal classes (don't walk them)
         fieldsCache += listOf(
             Any::class, String::class, Thread::class, Throwable::class, StackTraceElement::class,
-            WeakReference::class, ReferenceQueue::class, AbstractMap::class,
-            ReentrantReadWriteLock::class, SimpleDateFormat::class
+            WeakReference::class, ReferenceQueue::class, AbstractMap::class, Enum::class,
+            ReentrantLock::class, ReentrantReadWriteLock::class, SimpleDateFormat::class, ThreadPoolExecutor::class,
         )
             .map { it.java }
-            .associateWith { emptyList<Field>() }
+            .associateWith { emptyList() }
     }
 
     /*
@@ -78,9 +79,8 @@
         val path = ArrayList<String>()
         var cur = element
         while (true) {
-            val ref = visited.getValue(cur)
-            if (ref is Ref.RootRef) break
-            when (ref) {
+            when (val ref = visited.getValue(cur)) {
+                Ref.RootRef -> break
                 is Ref.FieldRef -> {
                     cur = ref.parent
                     path += "|${ref.parent.javaClass.simpleName}::${ref.name}"
@@ -89,7 +89,9 @@
                     cur = ref.parent
                     path += "[${ref.index}]"
                 }
-                else -> error("Should not be reached")
+                else -> {
+                    // Nothing, kludge for IDE
+                }
             }
         }
         path.reverse()
@@ -158,6 +160,13 @@
                     && !(it.type.isArray && it.type.componentType.isPrimitive)
                     && it.name != "previousOut" // System.out from TestBase that we store in a field to restore later
             }
+            check(fields.isEmpty() || !type.name.startsWith("java.")) {
+                """
+                    Trying to walk trough JDK's '$type' will get into illegal reflective access on JDK 9+.
+                    Either modify your test to avoid usage of this class or update FieldWalker code to retrieve 
+                    the captured state of this class without going through reflection (see how collections are handled).  
+                """.trimIndent()
+            }
             fields.forEach { it.isAccessible = true } // make them all accessible
             result.addAll(fields)
             type = type.superclass
diff --git a/kotlinx-coroutines-core/jvm/test/JobCancellationExceptionSerializerTest.kt b/kotlinx-coroutines-core/jvm/test/JobCancellationExceptionSerializerTest.kt
new file mode 100644
index 0000000..c909f27
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/JobCancellationExceptionSerializerTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+import java.io.*
+
+
+@Suppress("BlockingMethodInNonBlockingContext")
+class JobCancellationExceptionSerializerTest : TestBase() {
+
+    @Test
+    fun testSerialization() = runTest {
+        try {
+            coroutineScope {
+                expect(1)
+
+                launch {
+                    expect(2)
+                    try {
+                        hang {}
+                    } catch (e: CancellationException) {
+                        throw RuntimeException("RE2", e)
+                    }
+                }
+
+                launch {
+                    expect(3)
+                    throw RuntimeException("RE1")
+                }
+            }
+        } catch (e: Throwable) {
+            // Should not fail
+            ObjectOutputStream(ByteArrayOutputStream()).use {
+                it.writeObject(e)
+            }
+            finish(4)
+        }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/LimitedParallelismStressTest.kt b/kotlinx-coroutines-core/jvm/test/LimitedParallelismStressTest.kt
new file mode 100644
index 0000000..58f2b6b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/LimitedParallelismStressTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.*
+import org.junit.Test
+import org.junit.runner.*
+import org.junit.runners.*
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import kotlin.test.*
+
+@RunWith(Parameterized::class)
+class LimitedParallelismStressTest(private val targetParallelism: Int) : TestBase() {
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun params(): Collection<Array<Any>> = listOf(1, 2, 3, 4).map { arrayOf(it) }
+    }
+
+    @get:Rule
+    val executor = ExecutorRule(targetParallelism * 2)
+    private val iterations = 100_000
+
+    private val parallelism = AtomicInteger(0)
+
+    private fun checkParallelism() {
+        val value = parallelism.incrementAndGet()
+        Thread.yield()
+        assertTrue { value <= targetParallelism }
+        parallelism.decrementAndGet()
+    }
+
+    @Test
+    fun testLimitedExecutor() = runTest {
+        val view = executor.limitedParallelism(targetParallelism)
+        doStress {
+            repeat(iterations) {
+                launch(view) {
+                    checkParallelism()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testLimitedDispatchersIo() = runTest {
+        val view = Dispatchers.IO.limitedParallelism(targetParallelism)
+        doStress {
+            repeat(iterations) {
+                launch(view) {
+                    checkParallelism()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testLimitedDispatchersIoDispatchYield() = runTest {
+        val view = Dispatchers.IO.limitedParallelism(targetParallelism)
+        doStress {
+            launch(view) {
+                yield()
+                checkParallelism()
+            }
+        }
+    }
+
+    @Test
+    fun testLimitedExecutorReachesTargetParallelism() = runTest {
+        val view = executor.limitedParallelism(targetParallelism)
+        doStress {
+            repeat(iterations) {
+                val barrier = CyclicBarrier(targetParallelism + 1)
+                repeat(targetParallelism) {
+                    launch(view) {
+                        barrier.await()
+                    }
+                }
+                // Successfully awaited parallelism + 1
+                barrier.await()
+                coroutineContext.job.children.toList().joinAll()
+            }
+        }
+    }
+
+    private suspend inline fun doStress(crossinline block: suspend CoroutineScope.() -> Unit) {
+        repeat(stressTestMultiplier) {
+            coroutineScope {
+                block()
+            }
+        }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/LimitedParallelismUnhandledExceptionTest.kt b/kotlinx-coroutines-core/jvm/test/LimitedParallelismUnhandledExceptionTest.kt
new file mode 100644
index 0000000..8d48aa4
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/LimitedParallelismUnhandledExceptionTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class LimitedParallelismUnhandledExceptionTest : TestBase() {
+
+    @Test
+    fun testUnhandledException() = runTest {
+        var caughtException: Throwable? = null
+        val executor = Executors.newFixedThreadPool(
+            1
+        ) {
+            Thread(it).also {
+                it.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { _, e -> caughtException = e }
+            }
+        }.asCoroutineDispatcher()
+        val view = executor.limitedParallelism(1)
+        view.dispatch(EmptyCoroutineContext, Runnable { throw TestException() })
+        withContext(view) {
+            // Verify it is in working state and establish happens-before
+        }
+        assertTrue { caughtException is TestException }
+        executor.close()
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/RunBlockingJvmTest.kt b/kotlinx-coroutines-core/jvm/test/RunBlockingJvmTest.kt
new file mode 100644
index 0000000..057a8bb
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/RunBlockingJvmTest.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines
+
+import org.junit.*
+
+class RunBlockingJvmTest : TestBase() {
+    @Test
+    fun testContract() {
+        val rb: Int
+        runBlocking {
+            rb = 42
+        }
+        rb.hashCode() // unused
+    }
+}
+
diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt
index 61a2c8b..ce94d33 100644
--- a/kotlinx-coroutines-core/jvm/test/TestBase.kt
+++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt
@@ -20,15 +20,18 @@
  */
 public actual val isStressTest = System.getProperty("stressTest")?.toBoolean() ?: false
 
-public val stressTestMultiplierSqrt = if (isStressTest) 5 else 1
+public actual val stressTestMultiplierSqrt = if (isStressTest) 5 else 1
 
 private const val SHUTDOWN_TIMEOUT = 1_000L // 1s at most to wait per thread
 
+public actual val isNative = false
+
 /**
  * Multiply various constants in stress tests by this factor, so that they run longer during nightly stress test.
  */
 public actual val stressTestMultiplier = stressTestMultiplierSqrt * stressTestMultiplierSqrt
 
+
 @Suppress("ACTUAL_WITHOUT_EXPECT")
 public actual typealias TestResult = Unit
 
@@ -152,7 +155,8 @@
     })
 
     fun println(message: Any?) {
-        previousOut.println(message)
+        if (disableOutCheck) kotlin.io.println(message)
+        else previousOut.println(message)
     }
 
     @Before
@@ -199,15 +203,12 @@
     }
 
     fun initPoolsBeforeTest() {
-        CommonPool.usePrivatePool()
         DefaultScheduler.usePrivateScheduler()
     }
 
     fun shutdownPoolsAfterTest() {
-        CommonPool.shutdown(SHUTDOWN_TIMEOUT)
         DefaultScheduler.shutdown(SHUTDOWN_TIMEOUT)
-        DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT)
-        CommonPool.restore()
+        DefaultExecutor.shutdownForTests(SHUTDOWN_TIMEOUT)
         DefaultScheduler.restore()
     }
 
@@ -235,8 +236,9 @@
             if (expected != null) {
                 if (!expected(e))
                     error("Unexpected exception: $e", e)
-            } else
+            } else {
                 throw e
+            }
         } finally {
             if (ex == null && expected != null) error("Exception was expected but none produced")
         }
diff --git a/kotlinx-coroutines-core/jvm/test/TestBaseExtension.kt b/kotlinx-coroutines-core/jvm/test/TestBaseExtension.kt
new file mode 100644
index 0000000..799e559
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/TestBaseExtension.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines
+
+public actual fun TestBase.runMtTest(
+    expected: ((Throwable) -> Boolean)?,
+    unhandled: List<(Throwable) -> Boolean>,
+    block: suspend CoroutineScope.() -> Unit
+): TestResult = runTest(expected, unhandled, block)
diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt
index ea43c7a..ec45406 100644
--- a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt
@@ -54,7 +54,6 @@
         assertNull(myThreadLocal.get())
     }
 
-
     @Test
     fun testWithContext() = runTest {
         expect(1)
@@ -86,6 +85,77 @@
 
         finish(7)
     }
+
+    @Test
+    fun testNonCopyableElementReferenceInheritedOnLaunch() = runTest {
+        var parentElement: MyElement? = null
+        var inheritedElement: MyElement? = null
+
+        newSingleThreadContext("withContext").use {
+            withContext(it + MyElement(MyData())) {
+                parentElement = coroutineContext[MyElement.Key]
+                launch {
+                    inheritedElement = coroutineContext[MyElement.Key]
+                }
+            }
+        }
+
+        assertSame(inheritedElement, parentElement,
+            "Inner and outer coroutines did not have the same object reference to a" +
+                " ThreadContextElement that did not override `copyForChildCoroutine()`")
+    }
+
+    @Test
+    fun testCopyableElementCopiedOnLaunch() = runTest {
+        var parentElement: CopyForChildCoroutineElement? = null
+        var inheritedElement: CopyForChildCoroutineElement? = null
+
+        newSingleThreadContext("withContext").use {
+            withContext(it + CopyForChildCoroutineElement(MyData())) {
+                parentElement = coroutineContext[CopyForChildCoroutineElement.Key]
+                launch {
+                    inheritedElement = coroutineContext[CopyForChildCoroutineElement.Key]
+                }
+            }
+        }
+
+        assertNotSame(inheritedElement, parentElement,
+            "Inner coroutine did not copy its copyable ThreadContextElement.")
+    }
+
+    @Test
+    fun testCopyableThreadContextElementImplementsWriteVisibility() = runTest {
+        newFixedThreadPoolContext(nThreads = 4, name = "withContext").use {
+            withContext(it + CopyForChildCoroutineElement(MyData())) {
+                val forBlockData = MyData()
+                myThreadLocal.setForBlock(forBlockData) {
+                    assertSame(myThreadLocal.get(), forBlockData)
+                    launch {
+                        assertSame(myThreadLocal.get(), forBlockData)
+                    }
+                    launch {
+                        assertSame(myThreadLocal.get(), forBlockData)
+                        // Modify value in child coroutine. Writes to the ThreadLocal and
+                        // the (copied) ThreadLocalElement's memory are not visible to peer or
+                        // ancestor coroutines, so this write is both threadsafe and coroutinesafe.
+                        val innerCoroutineData = MyData()
+                        myThreadLocal.setForBlock(innerCoroutineData) {
+                            assertSame(myThreadLocal.get(), innerCoroutineData)
+                        }
+                        assertSame(myThreadLocal.get(), forBlockData) // Asserts value was restored.
+                    }
+                    launch {
+                        val innerCoroutineData = MyData()
+                        myThreadLocal.setForBlock(innerCoroutineData) {
+                            assertSame(myThreadLocal.get(), innerCoroutineData)
+                        }
+                        assertSame(myThreadLocal.get(), forBlockData)
+                    }
+                }
+                assertNull(myThreadLocal.get()) // Asserts value was restored to its origin
+            }
+        }
+    }
 }
 
 class MyData
@@ -114,3 +184,64 @@
         myThreadLocal.set(oldState)
     }
 }
+
+/**
+ * A [ThreadContextElement] that implements copy semantics in [copyForChild].
+ */
+class CopyForChildCoroutineElement(val data: MyData?) : CopyableThreadContextElement<MyData?> {
+    companion object Key : CoroutineContext.Key<CopyForChildCoroutineElement>
+
+    override val key: CoroutineContext.Key<CopyForChildCoroutineElement>
+        get() = Key
+
+    override fun updateThreadContext(context: CoroutineContext): MyData? {
+        val oldState = myThreadLocal.get()
+        myThreadLocal.set(data)
+        return oldState
+    }
+
+    override fun mergeForChild(overwritingElement: CoroutineContext.Element): CopyForChildCoroutineElement {
+        TODO("Not used in tests")
+    }
+
+    override fun restoreThreadContext(context: CoroutineContext, oldState: MyData?) {
+        myThreadLocal.set(oldState)
+    }
+
+    /**
+     * At coroutine launch time, the _current value of the ThreadLocal_ is inherited by the new
+     * child coroutine, and that value is copied to a new, unique, ThreadContextElement memory
+     * reference for the child coroutine to use uniquely.
+     *
+     * n.b. the value copied to the child must be the __current value of the ThreadLocal__ and not
+     * the value initially passed to the ThreadContextElement in order to reflect writes made to the
+     * ThreadLocal between coroutine resumption and the child coroutine launch point. Those writes
+     * will be reflected in the parent coroutine's [CopyForChildCoroutineElement] when it yields the
+     * thread and calls [restoreThreadContext].
+     */
+    override fun copyForChild(): CopyForChildCoroutineElement {
+        return CopyForChildCoroutineElement(myThreadLocal.get())
+    }
+}
+
+/**
+ * Calls [block], setting the value of [this] [ThreadLocal] for the duration of [block].
+ *
+ * When a [CopyForChildCoroutineElement] for `this` [ThreadLocal] is used within a
+ * [CoroutineContext], a ThreadLocal set this way will have the "correct" value expected lexically
+ * at every statement reached, whether that statement is reached immediately, across suspend and
+ * redispatch within one coroutine, or within a child coroutine. Writes made to the `ThreadLocal`
+ * by child coroutines will not be visible to the parent coroutine. Writes made to the `ThreadLocal`
+ * by the parent coroutine _after_ launching a child coroutine will not be visible to that child
+ * coroutine.
+ */
+private inline fun <ThreadLocalT, OutputT> ThreadLocal<ThreadLocalT>.setForBlock(
+    value: ThreadLocalT,
+    crossinline block: () -> OutputT
+) {
+    val priorValue = get()
+    set(value)
+    block()
+    set(priorValue)
+}
+
diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt
new file mode 100644
index 0000000..34e5955
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ThreadContextMutableCopiesTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2016-2022 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 ThreadContextMutableCopiesTest : TestBase() {
+    companion object {
+        val threadLocalData: ThreadLocal<MutableList<String>> = ThreadLocal.withInitial { ArrayList() }
+    }
+
+    class MyMutableElement(
+        val mutableData: MutableList<String>
+    ) : CopyableThreadContextElement<MutableList<String>> {
+
+        companion object Key : CoroutineContext.Key<MyMutableElement>
+
+        override val key: CoroutineContext.Key<*>
+            get() = Key
+
+        override fun updateThreadContext(context: CoroutineContext): MutableList<String> {
+            val st = threadLocalData.get()
+            threadLocalData.set(mutableData)
+            return st
+        }
+
+        override fun restoreThreadContext(context: CoroutineContext, oldState: MutableList<String>) {
+            threadLocalData.set(oldState)
+        }
+
+        override fun copyForChild(): MyMutableElement {
+            return MyMutableElement(ArrayList(mutableData))
+        }
+
+        override fun mergeForChild(overwritingElement: CoroutineContext.Element): MyMutableElement {
+            overwritingElement as MyMutableElement // <- app-specific, may be another subtype
+            return MyMutableElement((mutableData.toSet() + overwritingElement.mutableData).toMutableList())
+        }
+    }
+
+    @Test
+    fun testDataIsCopied() = runTest {
+        val root = MyMutableElement(ArrayList())
+        runBlocking(root) {
+            val data = threadLocalData.get()
+            expect(1)
+            launch(root) {
+                assertNotSame(data, threadLocalData.get())
+                assertEquals(data, threadLocalData.get())
+                finish(2)
+            }
+        }
+    }
+
+    @Test
+    fun testDataIsNotOverwritten() = runTest {
+        val root = MyMutableElement(ArrayList())
+        runBlocking(root) {
+            expect(1)
+            val originalData = threadLocalData.get()
+            threadLocalData.get().add("X")
+            launch {
+                threadLocalData.get().add("Y")
+                // Note here, +root overwrites the data
+                launch(Dispatchers.Default + root) {
+                    assertEquals(listOf("X", "Y"), threadLocalData.get())
+                    assertNotSame(originalData, threadLocalData.get())
+                    finish(2)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testDataIsMerged() = runTest {
+        val root = MyMutableElement(ArrayList())
+        runBlocking(root) {
+            expect(1)
+            val originalData = threadLocalData.get()
+            threadLocalData.get().add("X")
+            launch {
+                threadLocalData.get().add("Y")
+                // Note here, +root overwrites the data
+                launch(Dispatchers.Default + MyMutableElement(mutableListOf("Z"))) {
+                    assertEquals(listOf("X", "Y", "Z"), threadLocalData.get())
+                    assertNotSame(originalData, threadLocalData.get())
+                    finish(2)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testDataIsNotOverwrittenWithContext() = runTest {
+        val root = MyMutableElement(ArrayList())
+        runBlocking(root) {
+            val originalData = threadLocalData.get()
+            threadLocalData.get().add("X")
+            expect(1)
+            launch {
+                threadLocalData.get().add("Y")
+                // Note here, +root overwrites the data
+                withContext(Dispatchers.Default + root) {
+                    assertEquals(listOf("X", "Y"), threadLocalData.get())
+                    assertNotSame(originalData, threadLocalData.get())
+                    finish(2)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testDataIsCopiedForRunBlocking() = runTest {
+        val root = MyMutableElement(ArrayList())
+        val originalData = root.mutableData
+        runBlocking(root) {
+            assertNotSame(originalData, threadLocalData.get())
+        }
+    }
+
+    @Test
+    fun testDataIsCopiedForCoroutine() = runTest {
+        val root = MyMutableElement(ArrayList())
+        val originalData = root.mutableData
+        expect(1)
+        launch(root) {
+            assertNotSame(originalData, threadLocalData.get())
+            finish(2)
+        }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/ThreadLocalStressTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadLocalStressTest.kt
new file mode 100644
index 0000000..20621d1
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ThreadLocalStressTest.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 kotlinx.coroutines.sync.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+import kotlin.test.*
+
+
+class ThreadLocalStressTest : TestBase() {
+
+    private val threadLocal = ThreadLocal<String>()
+
+    // See the comment in doStress for the machinery
+    @Test
+    fun testStress() = runTest {
+        repeat (100 * stressTestMultiplierSqrt) {
+            withContext(Dispatchers.Default) {
+                repeat(100) {
+                    launch {
+                        doStress(null)
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testStressWithOuterValue() = runTest {
+        repeat (100 * stressTestMultiplierSqrt) {
+            withContext(Dispatchers.Default + threadLocal.asContextElement("bar")) {
+                repeat(100) {
+                    launch {
+                        doStress("bar")
+                    }
+                }
+            }
+        }
+    }
+
+    private suspend fun doStress(expectedValue: String?) {
+        assertEquals(expectedValue, threadLocal.get())
+        try {
+            /*
+             * Here we are using very specific code-path to trigger the execution we want to.
+             * The bug, in general, has a larger impact, but this particular code pinpoints it:
+             *
+             * 1) We use _undispatched_ withContext with thread element
+             * 2) We cancel the coroutine
+             * 3) We use 'suspendCancellableCoroutineReusable' that does _postponed_ cancellation check
+             *    which makes the reproduction of this race pretty reliable.
+             *
+             * Now the following code path is likely to be triggered:
+             *
+             * T1 from within 'withContinuationContext' method:
+             * Finds 'oldValue', finds undispatched completion, invokes its 'block' argument.
+             * 'block' is this coroutine, it goes to 'trySuspend', checks for postponed cancellation and *dispatches* it.
+             * The execution stops _right_ before 'undispatchedCompletion.clearThreadContext()'.
+             *
+             * T2 now executes the dispatched cancellation and concurrently mutates the state of the undispatched completion.
+             * All bets are off, now both threads can leave the thread locals state inconsistent.
+             */
+            withContext(threadLocal.asContextElement("foo")) {
+                yield()
+                cancel()
+                suspendCancellableCoroutineReusable<Unit> { }
+            }
+        } finally {
+            assertEquals(expectedValue, threadLocal.get())
+        }
+    }
+
+    /*
+     * Another set of tests for undispatcheable continuations that do not require stress test multiplier.
+     * Also note that `uncaughtExceptionHandler` is used as the only available mechanism to propagate error from
+     * `resumeWith`
+     */
+
+    @Test
+    fun testNonDispatcheableLeak() {
+        repeat(100) {
+            doTestWithPreparation(
+                ::doTest,
+                { threadLocal.set(null) }) { threadLocal.get() == null }
+            assertNull(threadLocal.get())
+        }
+    }
+
+    @Test
+    fun testNonDispatcheableLeakWithInitial() {
+        repeat(100) {
+            doTestWithPreparation(::doTest, { threadLocal.set("initial") }) { threadLocal.get() == "initial" }
+            assertEquals("initial", threadLocal.get())
+        }
+    }
+
+    @Test
+    fun testNonDispatcheableLeakWithContextSwitch() {
+        repeat(100) {
+            doTestWithPreparation(
+                ::doTestWithContextSwitch,
+                { threadLocal.set(null) }) { threadLocal.get() == null }
+            assertNull(threadLocal.get())
+        }
+    }
+
+    @Test
+    fun testNonDispatcheableLeakWithInitialWithContextSwitch() {
+        repeat(100) {
+            doTestWithPreparation(
+                ::doTestWithContextSwitch,
+                { threadLocal.set("initial") }) { true /* can randomly wake up on the non-main thread */ }
+            // Here we are always on the main thread
+            assertEquals("initial", threadLocal.get())
+        }
+    }
+
+    private fun doTestWithPreparation(testBody: suspend () -> Unit, setup: () -> Unit, isValid: () -> Boolean) {
+        setup()
+        val latch = CountDownLatch(1)
+        testBody.startCoroutineUninterceptedOrReturn(Continuation(EmptyCoroutineContext) {
+            if (!isValid()) {
+                Thread.currentThread().uncaughtExceptionHandler.uncaughtException(
+                    Thread.currentThread(),
+                    IllegalStateException("Unexpected error: thread local was not cleaned")
+                )
+            }
+            latch.countDown()
+        })
+        latch.await()
+    }
+
+    private suspend fun doTest() {
+        withContext(threadLocal.asContextElement("foo")) {
+            try {
+                coroutineScope {
+                    val semaphore = Semaphore(1, 1)
+                    cancel()
+                    semaphore.acquire()
+                }
+            } catch (e: CancellationException) {
+                // Ignore cancellation
+            }
+        }
+    }
+
+    private suspend fun doTestWithContextSwitch() {
+        withContext(threadLocal.asContextElement("foo")) {
+            try {
+                coroutineScope {
+                    val semaphore = Semaphore(1, 1)
+                    GlobalScope.launch { }.join()
+                    cancel()
+                    semaphore.acquire()
+                }
+            } catch (e: CancellationException) {
+                // Ignore cancellation
+            }
+        }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
index bd9a185..b4bc96e 100644
--- a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
+++ b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
@@ -11,14 +11,14 @@
 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)
+    DefaultExecutor.shutdownForTests(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)
+        DefaultExecutor.shutdownForTests(SHUTDOWN_TIMEOUT)
         testTimeSource.shutdown()
         timeSource = null // restore time source
     }
diff --git a/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt
index 377fcf4..cce77bb 100644
--- a/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt
@@ -59,7 +59,7 @@
     @Test
     fun testIgnoredTimeoutOnNullThrowsOnYield() = runTest {
         val value = withTimeoutOrNull(1) {
-            Thread.sleep(10)
+            Thread.sleep(75)
             yield()
         }
         assertNull(value)
diff --git a/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt
deleted file mode 100644
index 221120a..0000000
--- a/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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/ChannelSendReceiveStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt
index a6345cc..7e55f2e 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt
@@ -25,7 +25,10 @@
         fun params(): Collection<Array<Any>> =
                 listOf(1, 2, 10).flatMap { nSenders ->
                     listOf(1, 10).flatMap { nReceivers ->
-                        TestChannelKind.values().map { arrayOf(it, nSenders, nReceivers) }
+                        TestChannelKind.values()
+                            // Workaround for bug that won't be fixed unless new channel implementation, see #2443
+                            .filter { it != TestChannelKind.LINKED_LIST }
+                            .map { arrayOf(it, nSenders, nReceivers) }
                     }
                 }
     }
diff --git a/kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt
deleted file mode 100644
index a054175..0000000
--- a/kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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/examples/example-delay-01.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-01.kt
index d2a5d53..1bc3791 100644
--- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-01.kt
@@ -7,6 +7,7 @@
 
 import kotlinx.coroutines.*
 import kotlinx.coroutines.flow.*
+import kotlin.time.Duration.Companion.milliseconds
 
 fun main() = runBlocking {
 
diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-delay-02.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-02.kt
index f74422e..ac48af9 100644
--- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-02.kt
@@ -7,6 +7,7 @@
 
 import kotlinx.coroutines.*
 import kotlinx.coroutines.flow.*
+import kotlin.time.Duration.Companion.milliseconds
 
 fun main() = runBlocking {
 
diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-delay-03.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-03.kt
index edaea74..1fa9dc4 100644
--- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-03.kt
@@ -7,6 +7,7 @@
 
 import kotlinx.coroutines.*
 import kotlinx.coroutines.flow.*
+import kotlin.time.Duration.Companion.milliseconds
 
 fun main() = runBlocking {
 
diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-01.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-01.kt
index a19e6cb..8b3d2d2 100644
--- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-01.kt
@@ -1,4 +1,3 @@
-@file:OptIn(ExperimentalTime::class)
 /*
  * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
@@ -9,6 +8,7 @@
 import kotlin.time.*
 import kotlinx.coroutines.*
 import kotlinx.coroutines.flow.*
+import kotlin.time.Duration.Companion.milliseconds
 
 fun main() = runBlocking {
 
diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-02.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-02.kt
index 10ba88a..6500ecd 100644
--- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-02.kt
@@ -1,4 +1,3 @@
-@file:OptIn(ExperimentalTime::class)
 /*
  * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
@@ -9,6 +8,7 @@
 import kotlin.time.*
 import kotlinx.coroutines.*
 import kotlinx.coroutines.flow.*
+import kotlin.time.Duration.Companion.milliseconds
 
 fun main() = runBlocking {
 
diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-03.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-03.kt
index 5fa980a..4d5e40d 100644
--- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-03.kt
@@ -1,4 +1,3 @@
-@file:OptIn(ExperimentalTime::class)
 /*
  * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
@@ -9,6 +8,7 @@
 import kotlin.time.*
 import kotlinx.coroutines.*
 import kotlinx.coroutines.flow.*
+import kotlin.time.Duration.Companion.milliseconds
 
 fun main() = runBlocking {
 
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt
index cea9713..2095f14 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt
@@ -39,4 +39,16 @@
 
         finish(3)
     }
+
+    @Test
+    fun testLastDitchHandlerContainsContextualInformation() = runBlocking {
+        expect(1)
+        GlobalScope.launch(CoroutineName("last-ditch")) {
+            expect(2)
+            throw TestException()
+        }.join()
+        assertTrue(caughtException is TestException)
+        assertContains(caughtException.suppressed[0].toString(), "last-ditch")
+        finish(3)
+    }
 }
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt b/kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt
index 13023e3..4849f52 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt
@@ -15,7 +15,7 @@
  * but run only under JDK 1.8
  */
 @Suppress("ConflictingExtensionProperty")
-val Throwable.suppressed: Array<Throwable> get() {
+actual 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>
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/FlowSuppressionTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/FlowSuppressionTest.kt
new file mode 100644
index 0000000..41fe090
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/FlowSuppressionTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016-2021 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.flow.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class FlowSuppressionTest : TestBase() {
+    @Test
+    fun testSuppressionForPrimaryException() = runTest {
+        val flow = flow {
+            try {
+                emit(1)
+            } finally {
+                throw TestException()
+            }
+        }.catch { expectUnreached() }.onEach { throw TestException2() }
+
+        try {
+            flow.collect()
+        } catch (e: Throwable) {
+            assertIs<TestException>(e)
+            assertIs<TestException2>(e.suppressed[0])
+        }
+    }
+
+    @Test
+    fun testSuppressionForPrimaryExceptionRetry() = runTest {
+        val flow = flow {
+            try {
+                emit(1)
+            } finally {
+                throw TestException()
+            }
+        }.retry { expectUnreached(); true }.onEach { throw TestException2() }
+
+        try {
+            flow.collect()
+        } catch (e: Throwable) {
+            assertIs<TestException>(e)
+            assertIs<TestException2>(e.suppressed[0])
+
+        }
+    }
+
+    @Test
+    fun testCancellationSuppression() = runTest {
+        val flow = flow {
+            try {
+                expect(1)
+                emit(1)
+            } finally {
+                expect(3)
+                throw CancellationException("")
+            }
+        }.catch { expectUnreached() }.onEach {
+            expect(2)
+            throw TestException("")
+        }
+
+        try {
+            flow.collect()
+        } catch (e: Throwable) {
+            assertIs<TestException>(e)
+            assertIs<CancellationException>(e.suppressed[0])
+        }
+        finish(4)
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
index dba738a..d4e1904 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
@@ -124,4 +124,22 @@
         assertTrue(ex is CopyableWithCustomMessage)
         assertEquals("Recovered: [OK]", ex.message)
     }
+
+    @Test
+    fun testTryCopyThrows() = runTest {
+        class FailingException : Exception(), CopyableThrowable<FailingException> {
+            override fun createCopy(): FailingException? {
+                TODO("Not yet implemented")
+            }
+        }
+
+        val e = FailingException()
+        val result = runCatching {
+            coroutineScope<Unit> {
+                throw e
+            }
+        }
+
+        assertSame(e, result.exceptionOrNull())
+    }
 }
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt
index 2995466..f148a3a 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt
@@ -10,10 +10,11 @@
 import java.util.concurrent.*
 import kotlin.coroutines.*
 import kotlin.test.*
+import kotlin.time.Duration.Companion.minutes
 
 class WithContextCancellationStressTest : TestBase() {
 
-    private val iterations = 15_000 * stressTestMultiplier
+    private val timeoutAfter = 1.minutes
     private val pool = newFixedThreadPoolContext(3, "WithContextCancellationStressTest")
 
     @After
@@ -28,56 +29,54 @@
         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()
-                    }
+        withTimeout(timeoutAfter) {
+            while (eCnt == 0 || e1Cnt == 0 || e2Cnt == 0) {
+                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()
-                    }
+                        launch {
+                            barrier.await()
+                            e2 = true
+                            throw TestException2()
+                        }
 
-                    barrier.await()
-                    throw TestException()
+                        barrier.await()
+                        throw TestException()
+                    }
                 }
-            }
 
-            barrier.await()
+                barrier.await()
 
-            try {
-                jobWithContext.await()
-            } catch (e: Throwable) {
-                when (e) {
-                    is TestException -> {
-                        eCnt++
-                        e.checkSuppressed(e1 = e1, e2 =  e2)
+                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")
                     }
-                    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 {
diff --git a/kotlinx-coroutines-core/jvm/test/flow/SafeCollectorMemoryLeakTest.kt b/kotlinx-coroutines-core/jvm/test/flow/SafeCollectorMemoryLeakTest.kt
new file mode 100644
index 0000000..b75ec60
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/flow/SafeCollectorMemoryLeakTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016-2022 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 SafeCollectorMemoryLeakTest : TestBase() {
+    // custom List.forEach impl to avoid using iterator (FieldWalker cannot scan it)
+    private inline fun <T> List<T>.listForEach(action: (T) -> Unit) {
+        for (i in indices) action(get(i))
+    }
+
+    @Test
+    fun testCompletionIsProperlyCleanedUp() = runBlocking {
+        val job = flow {
+            emit(listOf(239))
+            expect(2)
+            hang {}
+        }.transform { l -> l.listForEach { _ -> emit(42) } }
+            .onEach { expect(1) }
+            .launchIn(this)
+        yield()
+        expect(3)
+        FieldWalker.assertReachableCount(0, job) { it == 239 }
+        job.cancelAndJoin()
+        finish(4)
+    }
+
+    @Test
+    fun testCompletionIsNotCleanedUp() = runBlocking {
+        val job = flow {
+            emit(listOf(239))
+            hang {}
+        }.transform { l -> l.listForEach { _ -> emit(42) } }
+            .onEach {
+                expect(1)
+                hang { finish(3) }
+            }
+            .launchIn(this)
+        yield()
+        expect(2)
+        FieldWalker.assertReachableCount(1, job) { it == 239 }
+        job.cancelAndJoin()
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/flow/SharedFlowStressTest.kt b/kotlinx-coroutines-core/jvm/test/flow/SharedFlowStressTest.kt
index 349b7c8..58e3ef4 100644
--- a/kotlinx-coroutines-core/jvm/test/flow/SharedFlowStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/flow/SharedFlowStressTest.kt
@@ -10,9 +10,8 @@
 import org.junit.Test
 import kotlin.collections.ArrayList
 import kotlin.test.*
-import kotlin.time.*
+import kotlin.time.Duration.Companion.seconds
 
-@ExperimentalTime
 class SharedFlowStressTest : TestBase() {
     private val nProducers = 5
     private val nConsumers = 3
@@ -84,4 +83,4 @@
         jobs.forEach { it.join() }
         println("total: produced = ${totalProduced.value}; consumed = ${totalConsumed.value}")
     }
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/flow/SharingStressTest.kt b/kotlinx-coroutines-core/jvm/test/flow/SharingStressTest.kt
index 7d346bd..25c0c98 100644
--- a/kotlinx-coroutines-core/jvm/test/flow/SharingStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/flow/SharingStressTest.kt
@@ -189,5 +189,9 @@
         var count = 0L
     }
 
-    private fun log(msg: String) = println("${testStarted.elapsedNow().toLongMilliseconds()} ms: $msg")
-}
\ No newline at end of file
+    private fun log(msg: String) = println("${testStarted.elapsedNow().inWholeMilliseconds} ms: $msg")
+
+    private fun MutableStateFlow<Int>.increment(delta: Int) {
+        update { it + delta }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
index 3daaf49..2cf0770 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
@@ -8,15 +8,15 @@
 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
+        repeat(5) { i ->
+            try {
+                // print a message twice a second
+                println("job: I'm sleeping $i ...")
+                delay(500)
+            } catch (e: Exception) {
+                // log the exception
+                println(e)
             }
         }
     }
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
index b1b9a9b..7d7f3bd 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
@@ -8,14 +8,16 @@
 import kotlinx.coroutines.*
 
 fun main() = runBlocking {
-    val job = launch {
-        try {
-            repeat(1000) { i ->
-                println("job: I'm sleeping $i ...")
-                delay(500L)
+    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
             }
-        } finally {
-            println("job: I'm running finally")
         }
     }
     delay(1300L) // delay a bit
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
index 9772ae5..97ea956 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
@@ -15,11 +15,7 @@
                 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")
-            }
+            println("job: I'm running finally")
         }
     }
     delay(1300L) // delay a bit
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
index e1afd05..ef66f5d 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
@@ -8,10 +8,22 @@
 import kotlinx.coroutines.*
 
 fun main() = runBlocking {
-    withTimeout(1300L) {
-        repeat(1000) { i ->
-            println("I'm sleeping $i ...")
-            delay(500L)
+    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-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
index 8c57b42..ea4a103 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
@@ -8,12 +8,10 @@
 import kotlinx.coroutines.*
 
 fun main() = runBlocking {
-    val result = withTimeoutOrNull(1300L) {
+    withTimeout(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-cancel-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt
index e7def13..79d0809 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt
@@ -7,25 +7,13 @@
 
 import kotlinx.coroutines.*
 
-var acquired = 0
-
-class Resource {
-    init { acquired++ } // Acquire the resource
-    fun close() { acquired-- } // Release the resource
-}
-
-fun main() {
-    runBlocking {
-        repeat(100_000) { // Launch 100K coroutines
-            launch { 
-                val resource = withTimeout(60) { // Timeout of 60 ms
-                    delay(50) // Delay for 50 ms
-                    Resource() // Acquire a resource and return it from withTimeout block     
-                }
-                resource.close() // Release the resource
-            }
+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
     }
-    // Outside of runBlocking all coroutines have completed
-    println(acquired) // Print the number of resources still acquired
+    println("Result is $result")
 }
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt
index 95424f5..13910a6 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt
@@ -18,16 +18,11 @@
     runBlocking {
         repeat(100_000) { // Launch 100K coroutines
             launch { 
-                var resource: Resource? = null // Not acquired yet
-                try {
-                    withTimeout(60) { // Timeout of 60 ms
-                        delay(50) // Delay for 50 ms
-                        resource = Resource() // Store a resource to the variable if acquired      
-                    }
-                    // We can do something else with the resource here
-                } finally {  
-                    resource?.close() // Release the resource if it was acquired
+                val resource = withTimeout(60) { // Timeout of 60 ms
+                    delay(50) // Delay for 50 ms
+                    Resource() // Acquire a resource and return it from withTimeout block     
                 }
+                resource.close() // Release the resource
             }
         }
     }
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt
new file mode 100644
index 0000000..336f5e3
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// This file was automatically generated from cancellation-and-timeouts.md by Knit tool. Do not edit.
+package kotlinx.coroutines.guide.exampleCancel10
+
+import kotlinx.coroutines.*
+
+var acquired = 0
+
+class Resource {
+    init { acquired++ } // Acquire the resource
+    fun close() { acquired-- } // Release the resource
+}
+
+fun main() {
+    runBlocking {
+        repeat(100_000) { // Launch 100K coroutines
+            launch { 
+                var resource: Resource? = null // Not acquired yet
+                try {
+                    withTimeout(60) { // Timeout of 60 ms
+                        delay(50) // Delay for 50 ms
+                        resource = Resource() // Store a resource to the variable if acquired      
+                    }
+                    // We can do something else with the resource here
+                } finally {  
+                    resource?.close() // Release the resource if it was acquired
+                }
+            }
+        }
+    }
+    // Outside of runBlocking all coroutines have completed
+    println(acquired) // Print the number of resources still acquired
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
index c6ad451..6796532 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
@@ -26,6 +26,6 @@
     }
     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?")
+    delay(1000) // delay the main thread for a second to see what happens
 }
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt
index 0cff63a..871e491 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt
@@ -34,24 +34,12 @@
     }
 
     @Test
-    fun testExampleCancel03() {
-        test("ExampleCancel03") { kotlinx.coroutines.guide.exampleCancel03.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 testExampleCancel04() {
         test("ExampleCancel04") { kotlinx.coroutines.guide.exampleCancel04.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."
         )
     }
@@ -64,14 +52,26 @@
             "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 testExampleCancel06() {
-        test("ExampleCancel06") { kotlinx.coroutines.guide.exampleCancel06.main() }.verifyLinesStartWith(
+        test("ExampleCancel06") { kotlinx.coroutines.guide.exampleCancel06.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 testExampleCancel07() {
+        test("ExampleCancel07") { kotlinx.coroutines.guide.exampleCancel07.main() }.verifyLinesStartWith(
             "I'm sleeping 0 ...",
             "I'm sleeping 1 ...",
             "I'm sleeping 2 ...",
@@ -80,8 +80,8 @@
     }
 
     @Test
-    fun testExampleCancel07() {
-        test("ExampleCancel07") { kotlinx.coroutines.guide.exampleCancel07.main() }.verifyLines(
+    fun testExampleCancel08() {
+        test("ExampleCancel08") { kotlinx.coroutines.guide.exampleCancel08.main() }.verifyLines(
             "I'm sleeping 0 ...",
             "I'm sleeping 1 ...",
             "I'm sleeping 2 ...",
@@ -90,8 +90,8 @@
     }
 
     @Test
-    fun testExampleCancel09() {
-        test("ExampleCancel09") { kotlinx.coroutines.guide.exampleCancel09.main() }.verifyLines(
+    fun testExampleCancel10() {
+        test("ExampleCancel10") { kotlinx.coroutines.guide.exampleCancel10.main() }.verifyLines(
             "0"
         )
     }
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
index 1a84fb9..e18741e 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
@@ -59,8 +59,8 @@
         test("ExampleContext06") { kotlinx.coroutines.guide.exampleContext06.main() }.verifyLines(
             "job1: I run in my own Job and execute independently!",
             "job2: I am a child of the request coroutine",
-            "job1: I am not affected by cancellation of the request",
-            "main: Who has survived request cancellation?"
+            "main: Who has survived request cancellation?",
+            "job1: I am not affected by cancellation of the request"
         )
     }
 
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt
deleted file mode 100644
index e69de29..0000000
--- a/kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt
+++ /dev/null
diff --git a/kotlinx-coroutines-core/jvm/test/internal/ConcurrentWeakMapOperationStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/ConcurrentWeakMapOperationStressTest.kt
index 49e6ccc..63b5838 100644
--- a/kotlinx-coroutines-core/jvm/test/internal/ConcurrentWeakMapOperationStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/internal/ConcurrentWeakMapOperationStressTest.kt
@@ -34,7 +34,7 @@
                 var generationOffset = 0L
                 while (!stop.value) {
                     val kvs = (generationOffset + batchSize * index until generationOffset + batchSize * (index + 1))
-                        .associateBy({ Key(it) }, {  it * it })
+                        .associateBy({ Key(it) }, { it * it })
                     generationOffset += batchSize * nThreads
                     for ((k, v) in kvs) {
                         assertEquals(null, m.put(k, v))
@@ -45,8 +45,8 @@
                     for ((k, v) in kvs) {
                         assertEquals(v, m.remove(k))
                     }
-                    for ((k, v) in kvs) {
-                        assertEquals(null, m.get(k))
+                    for ((k, _) in kvs) {
+                        assertEquals(null, m[k])
                     }
                     count.incrementAndGet()
                 }
@@ -68,6 +68,6 @@
         }
         stop.value = true
         threads.forEach { it.join() }
-        assertEquals(0, m.size)
+        assertEquals(0, m.size, "Unexpected map state: $m")
     }
-}
\ 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
index dde4b2f..a70a32b 100644
--- a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt
@@ -9,7 +9,6 @@
 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,
diff --git a/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapStressTest.kt
new file mode 100644
index 0000000..5bc0952
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapStressTest.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.internal
+
+import kotlinx.coroutines.*
+import java.util.concurrent.*
+import java.util.concurrent.CancellationException
+import kotlin.test.*
+
+class ThreadSafeHeapStressTest : TestBase() {
+    private class DisposableNode : EventLoopImplBase.DelayedTask(1L) {
+        override fun run() {
+        }
+    }
+
+    @Test
+    fun testConcurrentRemoveDispose() = runTest {
+        val heap = EventLoopImplBase.DelayedTaskQueue(1)
+        repeat(10_000 * stressTestMultiplierSqrt) {
+            withContext(Dispatchers.Default) {
+                val node = DisposableNode()
+                val barrier = CyclicBarrier(2)
+                launch {
+                    heap.addLast(node)
+                    barrier.await()
+                    heap.remove(node)
+                }
+                launch {
+                    barrier.await()
+                    Thread.yield()
+                    node.dispose()
+                }
+            }
+        }
+    }
+
+    @Test()
+    fun testConcurrentAddDispose() = runTest {
+        repeat(10_000 * stressTestMultiplierSqrt) {
+            val jobToCancel = Job()
+            val barrier = CyclicBarrier(2)
+            val jobToJoin = launch(Dispatchers.Default) {
+                barrier.await()
+                jobToCancel.cancelAndJoin()
+            }
+
+            try {
+                runBlocking { // Use event loop impl
+                    withContext(jobToCancel) {
+                        // This one is to work around heap allocation optimization
+                        launch(start = CoroutineStart.UNDISPATCHED) {
+                            delay(100_000)
+                        }
+                        barrier.await()
+                        delay(100_000)
+                    }
+                }
+            } catch (e: CancellationException) {
+                // Expected exception
+            }
+            jobToJoin.join()
+        }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt b/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt
index be7ed91..ee0960c 100644
--- a/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt
@@ -93,4 +93,4 @@
             assertEquals(set.size, h.size)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt b/kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt
index 2e61ec6..80c9803 100644
--- a/kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt
+++ b/kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt
@@ -26,9 +26,8 @@
 fun <R> test(name: String, block: () -> R): List<String> = outputException(name) {
     try {
         captureOutput(name, stdoutEnabled = OUT_ENABLED) { log ->
-            CommonPool.usePrivatePool()
             DefaultScheduler.usePrivateScheduler()
-            DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT)
+            DefaultExecutor.shutdownForTests(SHUTDOWN_TIMEOUT)
             resetCoroutineId()
             val threadsBefore = currentThreads()
             try {
@@ -39,15 +38,13 @@
             } finally {
                 // the shutdown
                 log.println("--- shutting down")
-                CommonPool.shutdown(SHUTDOWN_TIMEOUT)
                 DefaultScheduler.shutdown(SHUTDOWN_TIMEOUT)
                 shutdownDispatcherPools(SHUTDOWN_TIMEOUT)
-                DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT) // the last man standing -- cleanup all pending tasks
+                DefaultExecutor.shutdownForTests(SHUTDOWN_TIMEOUT) // the last man standing -- cleanup all pending tasks
             }
             checkTestThreads(threadsBefore) // check thread if the main completed successfully
         }
     } finally {
-        CommonPool.restore()
         DefaultScheduler.restore()
     }
 }
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/ResizableAtomicArrayLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/ResizableAtomicArrayLincheckTest.kt
new file mode 100644
index 0000000..1948a78
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/ResizableAtomicArrayLincheckTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.lincheck
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import org.jetbrains.kotlinx.lincheck.annotations.*
+import org.jetbrains.kotlinx.lincheck.paramgen.*
+
+@Param(name = "index", gen = IntGen::class, conf = "0:4")
+@Param(name = "value", gen = IntGen::class, conf = "1:5")
+@OpGroupConfig(name = "sync", nonParallel = true)
+class ResizableAtomicArrayLincheckTest : AbstractLincheckTest() {
+    private val a = ResizableAtomicArray<Int>(2)
+
+    @Operation
+    fun get(@Param(name = "index") index: Int): Int? = a[index]
+
+    @Operation(group = "sync")
+    fun set(@Param(name = "index") index: Int, @Param(name = "value") value: Int) {
+        a.setSynchronized(index, value)
+    }
+
+    override fun extractState() = (0..4).map { a[it] }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTerminationStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTerminationStressTest.kt
index 9c17e69..864ecdc 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTerminationStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTerminationStressTest.kt
@@ -10,7 +10,7 @@
 import java.util.concurrent.*
 
 class BlockingCoroutineDispatcherTerminationStressTest : TestBase() {
-    private val baseDispatcher = ExperimentalCoroutineDispatcher(
+    private val baseDispatcher = SchedulerCoroutineDispatcher(
         2, 20,
         TimeUnit.MILLISECONDS.toNanos(10)
     )
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt
index fe09440..f8830fe 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt
@@ -125,71 +125,6 @@
         checkPoolThreadsCreated(101..100 + CORES_COUNT)
     }
 
-    @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
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherWorkSignallingStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherWorkSignallingStressTest.kt
index 3280527..3b3e085 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherWorkSignallingStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherWorkSignallingStressTest.kt
@@ -18,7 +18,7 @@
         val iterations = 1000 * stressTestMultiplier
         repeat(iterations) {
             // Create a dispatcher every iteration to increase probability of race
-            val dispatcher = ExperimentalCoroutineDispatcher(CORES_COUNT)
+            val dispatcher = SchedulerCoroutineDispatcher(CORES_COUNT)
             val blockingDispatcher = dispatcher.blocking(100)
 
             val blockingBarrier = CyclicBarrier(CORES_COUNT * 3 + 1)
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt
index 3cd77da..c95415a 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt
@@ -134,7 +134,7 @@
         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")
+        val dispatcher = SchedulerCoroutineDispatcher(1, 1, IDLE_WORKER_KEEP_ALIVE_NS, "SomeTestName")
         dispatcher.use {
             launch(dispatcher) {
             }.join()
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt
index 473b429..a50867d 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt
@@ -22,15 +22,13 @@
         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 lateinit var closeableDispatcher: SchedulerCoroutineDispatcher
+    private lateinit var dispatcher: CoroutineDispatcher
 
     private val started = atomic(0)
     private val finished = atomic(0)
@@ -44,20 +42,12 @@
         }
     }
 
-    @Test
-    fun testRacingClose() {
-        repeat(N_REPEAT) {
-            closeIndex = rnd.nextInt(N_COROS)
-            launchCoroutines()
-        }
-    }
-
     private fun launchCoroutines() = runBlocking {
-        closeableDispatcher = ExperimentalCoroutineDispatcher(N_THREADS)
+        closeableDispatcher = SchedulerCoroutineDispatcher(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
+            Mode.CPU_LIMITED -> closeableDispatcher.limitedParallelism(N_THREADS)
+            Mode.BLOCKING -> closeableDispatcher.blocking(N_THREADS)
         }
         started.value = 0
         finished.value = 0
@@ -68,20 +58,16 @@
         assertEquals(N_COROS, finished.value)
     }
 
+    // Index and level are used only for debugging purpose
     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)
-                    val t = Thread.currentThread()
-                    if (!t.name.contains("DefaultDispatcher-worker")) {
-                        val a = 2
-                    }
                 } else {
                     yield()
                 }
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt
index cb49f05..7aefd4f 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt
@@ -15,7 +15,7 @@
 import kotlin.test.*
 
 class CoroutineSchedulerStressTest : TestBase() {
-    private var dispatcher: ExperimentalCoroutineDispatcher = ExperimentalCoroutineDispatcher()
+    private var dispatcher: SchedulerCoroutineDispatcher = SchedulerCoroutineDispatcher()
     private val observedThreads = ConcurrentHashMap<Thread, Long>()
     private val tasksNum = 500_000 * stressMemoryMultiplier()
 
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt
index b0a5954..9d41c05 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt
@@ -106,22 +106,22 @@
 
     @Test(expected = IllegalArgumentException::class)
     fun testNegativeCorePoolSize() {
-        ExperimentalCoroutineDispatcher(-1, 4)
+        SchedulerCoroutineDispatcher(-1, 4)
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun testNegativeMaxPoolSize() {
-        ExperimentalCoroutineDispatcher(1, -4)
+        SchedulerCoroutineDispatcher(1, -4)
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun testCorePoolSizeGreaterThanMaxPoolSize() {
-        ExperimentalCoroutineDispatcher(4, 1)
+        SchedulerCoroutineDispatcher(4, 1)
     }
 
     @Test
     fun testSelfClose() {
-        val dispatcher = ExperimentalCoroutineDispatcher(1, 1)
+        val dispatcher = SchedulerCoroutineDispatcher(1, 1)
         val latch = CountDownLatch(1)
         dispatcher.dispatch(EmptyCoroutineContext, Runnable {
             dispatcher.close(); latch.countDown()
@@ -131,7 +131,7 @@
 
     @Test
     fun testInterruptionCleanup() {
-        ExperimentalCoroutineDispatcher(1, 1).use {
+        SchedulerCoroutineDispatcher(1, 1).use {
             val executor = it.executor
             var latch = CountDownLatch(1)
             executor.execute {
@@ -171,4 +171,4 @@
     private class TaskContextImpl(override val taskMode: Int) : TaskContext {
         override fun afterTask() {}
     }
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/DefaultDispatchersTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/DefaultDispatchersTest.kt
new file mode 100644
index 0000000..56c6695
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/DefaultDispatchersTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.scheduling
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import kotlin.test.*
+
+class DefaultDispatchersTest : TestBase() {
+
+    private /*const*/ val EXPECTED_PARALLELISM = 64
+
+    @Test(timeout = 10_000L)
+    fun testLimitedParallelismIsSeparatedFromDefaultIo() = runTest {
+        val barrier = CyclicBarrier(EXPECTED_PARALLELISM + 1)
+        val ioBlocker = CountDownLatch(1)
+        repeat(EXPECTED_PARALLELISM) {
+            launch(Dispatchers.IO) {
+                barrier.await()
+                ioBlocker.await()
+            }
+        }
+
+        barrier.await() // Ensure all threads are occupied
+        barrier.reset()
+        val limited = Dispatchers.IO.limitedParallelism(EXPECTED_PARALLELISM)
+        repeat(EXPECTED_PARALLELISM) {
+            launch(limited) {
+                barrier.await()
+            }
+        }
+        barrier.await()
+        ioBlocker.countDown()
+    }
+
+    @Test(timeout = 10_000L)
+    fun testDefaultDispatcherIsSeparateFromIO() = runTest {
+        val ioBarrier = CyclicBarrier(EXPECTED_PARALLELISM + 1)
+        val ioBlocker = CountDownLatch(1)
+        repeat(EXPECTED_PARALLELISM) {
+            launch(Dispatchers.IO) {
+                ioBarrier.await()
+                ioBlocker.await()
+            }
+        }
+
+        ioBarrier.await() // Ensure all threads are occupied
+        val parallelism = Runtime.getRuntime().availableProcessors()
+        val defaultBarrier = CyclicBarrier(parallelism + 1)
+        repeat(parallelism) {
+            launch(Dispatchers.Default) {
+                defaultBarrier.await()
+            }
+        }
+        defaultBarrier.await()
+        ioBlocker.countDown()
+    }
+
+    @Test
+    fun testHardCapOnParallelism() = runTest {
+        val iterations = 100_000 * stressTestMultiplierSqrt
+        val concurrency = AtomicInteger()
+        repeat(iterations) {
+            launch(Dispatchers.IO) {
+                val c = concurrency.incrementAndGet()
+                assertTrue("Got: $c") { c <= EXPECTED_PARALLELISM }
+                concurrency.decrementAndGet()
+            }
+        }
+    }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt
index b492427..e570580 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt
@@ -11,11 +11,6 @@
 class LimitingDispatcherTest : SchedulerTestBase() {
 
     @Test(expected = IllegalArgumentException::class)
-    fun testTooLargeView() {
-        view(corePoolSize + 1)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
     fun testNegativeView() {
         view(-1)
     }
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt b/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt
index dd969bd..fc4436f 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt
@@ -6,7 +6,6 @@
 
 package kotlinx.coroutines.scheduling
 
-import kotlinx.atomicfu.*
 import kotlinx.coroutines.*
 import kotlinx.coroutines.internal.*
 import org.junit.*
@@ -61,11 +60,11 @@
     protected var maxPoolSize = 1024
     protected var idleWorkerKeepAliveNs = IDLE_WORKER_KEEP_ALIVE_NS
 
-    private var _dispatcher: ExperimentalCoroutineDispatcher? = null
+    private var _dispatcher: SchedulerCoroutineDispatcher? = null
     protected val dispatcher: CoroutineDispatcher
         get() {
             if (_dispatcher == null) {
-                _dispatcher = ExperimentalCoroutineDispatcher(
+                _dispatcher = SchedulerCoroutineDispatcher(
                     corePoolSize,
                     maxPoolSize,
                     idleWorkerKeepAliveNs
@@ -86,7 +85,7 @@
 
     protected fun view(parallelism: Int): CoroutineDispatcher {
         val intitialize = dispatcher
-        return _dispatcher!!.limited(parallelism)
+        return _dispatcher!!.limitedParallelism(parallelism)
     }
 
     @After
@@ -98,3 +97,17 @@
         }
     }
 }
+
+internal fun SchedulerCoroutineDispatcher.blocking(parallelism: Int = 16): CoroutineDispatcher {
+    return object : CoroutineDispatcher() {
+
+        @InternalCoroutinesApi
+        override fun dispatchYield(context: CoroutineContext, block: Runnable) {
+            this@blocking.dispatchWithContext(block, BlockingContext, true)
+        }
+
+        override fun dispatch(context: CoroutineContext, block: Runnable) {
+            this@blocking.dispatchWithContext(block, BlockingContext, false)
+        }
+    }.limitedParallelism(parallelism)
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt
index 6a66da9..743b4a6 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt
@@ -13,8 +13,8 @@
 
     @Test
     fun testSharedThread() = runTest {
-        val dispatcher = ExperimentalCoroutineDispatcher(1, schedulerName = "first")
-        val dispatcher2 = ExperimentalCoroutineDispatcher(1, schedulerName = "second")
+        val dispatcher = SchedulerCoroutineDispatcher(1, schedulerName = "first")
+        val dispatcher2 = SchedulerCoroutineDispatcher(1, schedulerName = "second")
 
         try {
             withContext(dispatcher) {
@@ -39,7 +39,7 @@
         val cores = Runtime.getRuntime().availableProcessors()
         repeat(cores + 1) {
             CoroutineScope(Dispatchers.Default).launch {
-                ExperimentalCoroutineDispatcher(1).close()
+                SchedulerCoroutineDispatcher(1).close()
             }.join()
         }
     }
diff --git a/kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt b/kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt
deleted file mode 100644
index 4a6f4d2..0000000
--- a/kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * 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 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(expectedError === e, "Expected to be thrown: '$expectedError' but was '$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(expectedError === e, "Expected to be thrown: '$expectedError' but was '$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
index 30c187f..f5b2222 100644
--- a/kotlinx-coroutines-core/native/src/Builders.kt
+++ b/kotlinx-coroutines-core/native/src/Builders.kt
@@ -2,11 +2,14 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
+@file:OptIn(ExperimentalContracts::class)
 package kotlinx.coroutines
 
 import kotlinx.cinterop.*
 import platform.posix.*
+import kotlin.contracts.*
 import kotlin.coroutines.*
+import kotlin.native.concurrent.*
 
 /**
  * Runs new coroutine and **blocks** current thread _interruptibly_ until its completion.
@@ -30,10 +33,13 @@
  * @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 {
+public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
+    contract {
+        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
+    }
     val contextInterceptor = context[ContinuationInterceptor]
     val eventLoop: EventLoop?
-    var newContext: CoroutineContext = context // todo: kludge for data flow analysis error
+    val newContext: CoroutineContext
     if (contextInterceptor == null) {
         // create or use private event loop if no dispatcher is specified
         eventLoop = ThreadLocalEventLoop.eventLoop
@@ -54,20 +60,33 @@
     parentContext: CoroutineContext,
     private val eventLoop: EventLoop?
 ) : AbstractCoroutine<T>(parentContext, true, true) {
+    private val joinWorker = Worker.current
+
     override val isScopedCoroutine: Boolean get() = true
 
+    override fun afterCompletion(state: Any?) {
+        // wake up blocked thread
+        if (joinWorker != Worker.current) {
+            // Unpark waiting worker
+            joinWorker.executeAfter(0L, {}) // send an empty task to unpark the waiting event loop
+        }
+    }
+
     @Suppress("UNCHECKED_CAST")
-    fun joinBlocking(): T = memScoped {
+    fun joinBlocking(): T {
         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
+                var parkNanos: Long
+                // Workaround for bug in BE optimizer that cannot eliminate boxing here
+                if (eventLoop != null) {
+                    parkNanos = eventLoop.processNextEvent()
+                } else {
+                    parkNanos = Long.MAX_VALUE
+                }
+                // note: processNextEvent may lose 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)
+                joinWorker.park(parkNanos / 1000L, true)
             }
         } finally { // paranoia
             eventLoop?.decrementUseCount()
@@ -75,6 +94,6 @@
         // now return result
         val state = state.unboxState()
         (state as? CompletedExceptionally)?.let { throw it.cause }
-        state as T
+        return state as T
     }
 }
diff --git a/kotlinx-coroutines-core/native/src/CloseableCoroutineDispatcher.kt b/kotlinx-coroutines-core/native/src/CloseableCoroutineDispatcher.kt
new file mode 100644
index 0000000..0e239a4
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/CloseableCoroutineDispatcher.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+public actual abstract class CloseableCoroutineDispatcher actual constructor() : CoroutineDispatcher() {
+    public actual abstract fun close()
+}
diff --git a/kotlinx-coroutines-core/native/src/CompletionHandler.kt b/kotlinx-coroutines-core/native/src/CompletionHandler.kt
deleted file mode 100644
index 4835f79..0000000
--- a/kotlinx-coroutines-core/native/src/CompletionHandler.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines
-
-import kotlinx.coroutines.internal.*
-
-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
index 47afd8a..6e2dac1 100644
--- a/kotlinx-coroutines-core/native/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/native/src/CoroutineContext.kt
@@ -8,33 +8,49 @@
 import kotlin.coroutines.*
 import kotlin.native.concurrent.*
 
-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, context: CoroutineContext): DisposableHandle =
-        takeEventLoop().invokeOnTimeout(timeMillis, block, context)
 
-    actual fun enqueue(task: Runnable): Unit = loopWasShutDown()
+    private val delegate = WorkerDispatcher(name = "DefaultExecutor")
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        checkState()
+        delegate.dispatch(context, block)
+    }
+
+    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+        checkState()
+        delegate.scheduleResumeAfterDelay(timeMillis, continuation)
+    }
+
+    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
+        checkState()
+        return delegate.invokeOnTimeout(timeMillis, block, context)
+    }
+
+    actual fun enqueue(task: Runnable): Unit {
+        checkState()
+        delegate.dispatch(EmptyCoroutineContext, task)
+    }
+
+    private fun checkState() {
+        if (multithreadingSupported) return
+        error("DefaultExecutor should never be invoked in K/N with disabled new memory model. The top-most 'runBlocking' event loop has been shutdown")
+    }
 }
 
-internal fun loopWasShutDown(): Nothing = error("Cannot execute task because event loop was shut down")
-
-internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
-    DefaultExecutor
+internal expect fun createDefaultDispatcher(): CoroutineDispatcher
 
 @SharedImmutable
-internal actual val DefaultDelay: Delay = DefaultExecutor
+internal actual val DefaultDelay: Delay = if (multithreadingSupported) DefaultExecutor else OldDefaultExecutor
 
 public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
     val combined = coroutineContext + context
-    return if (combined !== DefaultExecutor && combined[ContinuationInterceptor] == null)
-        combined + DefaultExecutor else combined
+    return if (combined !== DefaultDelay && combined[ContinuationInterceptor] == null)
+        combined + (DefaultDelay as CoroutineContext.Element) else combined
+}
+
+public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext {
+    return this + addedContext
 }
 
 // No debugging facilities on native
diff --git a/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt
index b0aa863..434813d 100644
--- a/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt
+++ b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt
@@ -5,8 +5,10 @@
 package kotlinx.coroutines
 
 import kotlin.coroutines.*
+import kotlin.native.*
 
+@OptIn(ExperimentalStdlibApi::class)
 internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
     // log exception
-    exception.printStackTrace()
+    processUnhandledException(exception)
 }
diff --git a/kotlinx-coroutines-core/native/src/Dispatchers.kt b/kotlinx-coroutines-core/native/src/Dispatchers.kt
index 4e5facf..6c51a03 100644
--- a/kotlinx-coroutines-core/native/src/Dispatchers.kt
+++ b/kotlinx-coroutines-core/native/src/Dispatchers.kt
@@ -4,15 +4,54 @@
 
 package kotlinx.coroutines
 
+import kotlinx.coroutines.internal.multithreadingSupported
 import kotlin.coroutines.*
 
 public actual object Dispatchers {
-    public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
-    public actual val Main: MainCoroutineDispatcher = NativeMainDispatcher(Default)
+    public actual val Default: CoroutineDispatcher = createDefaultDispatcherBasedOnMm()
+    public actual val Main: MainCoroutineDispatcher
+        get() = injectedMainDispatcher ?: mainDispatcher
     public actual val Unconfined: CoroutineDispatcher get() = kotlinx.coroutines.Unconfined // Avoid freezing
+
+    private val mainDispatcher = createMainDispatcher(Default)
+
+    private var injectedMainDispatcher: MainCoroutineDispatcher? = null
+
+    @PublishedApi
+    internal fun injectMain(dispatcher: MainCoroutineDispatcher) {
+        if (!multithreadingSupported) {
+            throw IllegalStateException("Dispatchers.setMain is not supported in Kotlin/Native when new memory model is disabled")
+        }
+        injectedMainDispatcher = dispatcher
+    }
+
+    @PublishedApi
+    internal fun resetInjectedMain() {
+        injectedMainDispatcher = null
+    }
 }
 
-private class NativeMainDispatcher(val delegate: CoroutineDispatcher) : MainCoroutineDispatcher() {
+internal expect fun createMainDispatcher(default: CoroutineDispatcher): MainCoroutineDispatcher
+
+private fun createDefaultDispatcherBasedOnMm(): CoroutineDispatcher {
+    return if (multithreadingSupported) createDefaultDispatcher()
+    else OldDefaultExecutor
+}
+
+private fun takeEventLoop(): EventLoopImpl =
+    ThreadLocalEventLoop.currentOrNull() as? EventLoopImpl ?:
+    error("There is no event loop. Use runBlocking { ... } to start one.")
+
+internal object OldDefaultExecutor : 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, context: CoroutineContext): DisposableHandle =
+        takeEventLoop().invokeOnTimeout(timeMillis, block, context)
+}
+
+internal class OldMainDispatcher(private 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)
diff --git a/kotlinx-coroutines-core/native/src/EventLoop.kt b/kotlinx-coroutines-core/native/src/EventLoop.kt
index 925cbe9..f4e5b8c 100644
--- a/kotlinx-coroutines-core/native/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/native/src/EventLoop.kt
@@ -4,18 +4,38 @@
 
 package kotlinx.coroutines
 
+import kotlinx.cinterop.*
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.internal.multithreadingSupported
+import platform.posix.*
 import kotlin.coroutines.*
+import kotlin.native.concurrent.*
 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 actual abstract class EventLoopImplPlatform : EventLoop() {
+
+    private val current = Worker.current
+
+    protected actual fun unpark() {
+        current.executeAfter(0L, {})// send an empty task to unpark the waiting event loop
+    }
+
+    protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) {
+        if (multithreadingSupported) {
+            DefaultExecutor.invokeOnTimeout(now, delayedTask, EmptyCoroutineContext)
+        } else {
+            error("Cannot execute task because event loop was shut down")
+        }
+    }
 }
 
 internal class EventLoopImpl: EventLoopImplBase() {
-    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
-        scheduleInvokeOnTimeout(timeMillis, block)
+    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
+        if (!multithreadingSupported) {
+            return scheduleInvokeOnTimeout(timeMillis, block)
+        }
+        return DefaultDelay.invokeOnTimeout(timeMillis, block, context)
+    }
 }
 
 internal actual fun createEventLoop(): EventLoop = EventLoopImpl()
diff --git a/kotlinx-coroutines-core/native/src/Exceptions.kt b/kotlinx-coroutines-core/native/src/Exceptions.kt
index da9979b..1a923c4 100644
--- a/kotlinx-coroutines-core/native/src/Exceptions.kt
+++ b/kotlinx-coroutines-core/native/src/Exceptions.kt
@@ -4,6 +4,9 @@
 
 package kotlinx.coroutines
 
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.internal.SuppressSupportingThrowableImpl
+
 /**
  * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending.
  * It indicates _normal_ cancellation of a coroutine.
@@ -31,7 +34,9 @@
 }
 
 @Suppress("NOTHING_TO_INLINE")
-internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) { /* empty */ }
+internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) {
+    if (this is SuppressSupportingThrowableImpl) addSuppressed(other)
+}
 
 // For use in tests
 internal actual val RECOVER_STACK_TRACES: Boolean = false
diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt
new file mode 100644
index 0000000..1c1306c
--- /dev/null
+++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.native.concurrent.*
+
+@ExperimentalCoroutinesApi
+public actual fun newSingleThreadContext(name: String): CloseableCoroutineDispatcher {
+    if (!multithreadingSupported) throw IllegalStateException("This API is only supported for experimental K/N memory model")
+    return WorkerDispatcher(name)
+}
+
+public actual fun newFixedThreadPoolContext(nThreads: Int, name: String): CloseableCoroutineDispatcher {
+    if (!multithreadingSupported) throw IllegalStateException("This API is only supported for experimental K/N memory model")
+    require(nThreads >= 1) { "Expected at least one thread, but got: $nThreads"}
+    return MultiWorkerDispatcher(name, nThreads)
+}
+
+internal class WorkerDispatcher(name: String) : CloseableCoroutineDispatcher(), Delay {
+    private val worker = Worker.start(name = name)
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        worker.executeAfter(0L) { block.run() }
+    }
+
+    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+        worker.executeAfter(timeMillis.toMicrosSafe()) {
+            with(continuation) { resumeUndispatched(Unit) }
+        }
+    }
+
+    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
+        // Workers don't have an API to cancel sent "executeAfter" block, but we are trying
+        // to control the damage and reduce reachable objects by nulling out `block`
+        // that may retain a lot of references, and leaving only an empty shell after a timely disposal
+        // This is a class and not an object with `block` in a closure because that would defeat the purpose.
+        class DisposableBlock(block: Runnable) : DisposableHandle, Function0<Unit> {
+            private val disposableHolder = AtomicReference<Runnable?>(block)
+
+            override fun invoke() {
+                disposableHolder.value?.run()
+            }
+
+            override fun dispose() {
+                disposableHolder.value = null
+            }
+        }
+
+        val disposableBlock = DisposableBlock(block)
+        worker.executeAfter(timeMillis.toMicrosSafe(), disposableBlock)
+        return disposableBlock
+    }
+
+    override fun close() {
+        worker.requestTermination().result // Note: calling "result" blocks
+    }
+
+    private fun Long.toMicrosSafe(): Long {
+        val result = this * 1000
+        return if (result > this) result else Long.MAX_VALUE
+    }
+}
+
+private class MultiWorkerDispatcher(name: String, workersCount: Int) : CloseableCoroutineDispatcher() {
+    private val tasksQueue = Channel<Runnable>(Channel.UNLIMITED)
+    private val workers = Array(workersCount) { Worker.start(name = "$name-$it") }
+
+    init {
+        workers.forEach { w -> w.executeAfter(0L) { workerRunLoop() } }
+    }
+
+    private fun workerRunLoop() = runBlocking {
+        for (task in tasksQueue) {
+            // TODO error handling
+            task.run()
+        }
+    }
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        // TODO handle rejections
+        tasksQueue.trySend(block)
+    }
+
+    override fun close() {
+        tasksQueue.close()
+        workers.forEach { it.requestTermination().result }
+    }
+}
diff --git a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
index b379c6a..f6e18dd 100644
--- a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
+++ b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
@@ -4,15 +4,35 @@
 
 package kotlinx.coroutines.internal
 
-internal actual typealias ReentrantLock = NoOpLock
+import kotlinx.atomicfu.*
+import kotlin.native.concurrent.*
+import kotlinx.atomicfu.locks.withLock as withLock2
 
-internal actual inline fun <T> ReentrantLock.withLock(action: () -> T) = action()
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+internal actual typealias ReentrantLock = kotlinx.atomicfu.locks.SynchronizedObject
 
-internal class NoOpLock {
-    fun tryLock() = true
-    fun unlock(): Unit {}
-}
+internal actual inline fun <T> ReentrantLock.withLock(action: () -> T): T = this.withLock2(action)
 
 internal actual fun <E> subscriberList(): MutableList<E> = CopyOnWriteList<E>()
 
 internal actual fun <E> identitySet(expectedSize: Int): MutableSet<E> = HashSet()
+
+
+// "Suppress-supporting throwable" is currently used for tests only
+internal open class SuppressSupportingThrowableImpl : Throwable() {
+    private val _suppressed = atomic<Array<Throwable>>(emptyArray())
+
+    val suppressed: Array<Throwable>
+        get() = _suppressed.value
+
+    fun addSuppressed(other: Throwable) {
+        _suppressed.update { current ->
+            current + other
+        }
+    }
+}
+
+// getter instead of a property due to the bug in the initialization dependencies tracking with '-Xir-property-lazy-initialization=disabled' that Ktor uses 
+@OptIn(ExperimentalStdlibApi::class)
+internal val multithreadingSupported: Boolean
+    get() = kotlin.native.isExperimentalMM()
diff --git a/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
index 30f063a..2896c2e 100644
--- a/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
+++ b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
@@ -4,101 +4,76 @@
 
 package kotlinx.coroutines.internal
 
-@Suppress("UNCHECKED_CAST")
-internal class CopyOnWriteList<E>(private var array: Array<Any?> = arrayOfNulls(4)) : AbstractMutableList<E>() {
+import kotlinx.atomicfu.*
 
-    private var _size = 0
-    override val size: Int get() = _size
+@Suppress("UNCHECKED_CAST")
+internal class CopyOnWriteList<E> : AbstractMutableList<E>() {
+
+    private val _array = atomic(arrayOfNulls<Any?>(0))
+    private var array: Array<Any?>
+        get() = _array.value
+        set(value) { _array.value = value }
+
+    override val size: Int
+        get() = array.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
+        val n = size
+        val update = array.copyOf(n + 1)
+        update[n] = 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
-        )
+        val n = size
+        val update = arrayOfNulls<Any?>(n + 1)
+        array.copyInto(destination = update, endIndex = index)
         update[index] = element
-        array.copyInto(
-            destination = update,
-            destinationOffset = index + 1,
-            startIndex = index,
-            endIndex = _size + 1
-        )
-        ++_size
+        array.copyInto(destination = update, destinationOffset = index + 1, startIndex = index, endIndex = n + 1)
         array = update
     }
 
     override fun remove(element: E): Boolean {
         val index = array.indexOf(element as Any)
-        if (index == -1) {
-            return false
-        }
-
+        if (index == -1) return false
         removeAt(index)
         return true
     }
 
     override fun removeAt(index: Int): E {
         rangeCheck(index)
-        modCount++
-        val n = array.size
+        val n = 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
-        )
+        val update = arrayOfNulls<Any>(n - 1)
+        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 iterator(): MutableIterator<E> = IteratorImpl(array as Array<E>)
     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
 
-    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 class IteratorImpl<E>(private val array: Array<E>) : MutableIterator<E> {
         private var current = 0
 
-        override fun hasNext(): Boolean = current != size
+        override fun hasNext(): Boolean = current != array.size
 
         override fun next(): E {
-            if (!hasNext()) {
-                throw NoSuchElementException()
-            }
-
-            return array[current++]!!
+            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")
-        }
+        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
deleted file mode 100644
index a8aed04..0000000
--- a/kotlinx-coroutines-core/native/src/internal/LinkedList.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-@file:Suppress("NO_EXPLICIT_RETURN_TYPE_IN_API_MODE", "NO_EXPLICIT_VISIBILITY_IN_API_MODE")
-
-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() {
-    override val affectedNode: Node get() = queue._prev
-    actual override fun finishPrepare(prepareOp: PrepareOp) {}
-    override fun onComplete() = queue.addLast(node)
-    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")
-    actual val result: T get() = affectedNode as T
-    override val affectedNode: Node = queue.nextNode
-    actual override fun finishPrepare(prepareOp: PrepareOp) {}
-    override fun onComplete() { queue.removeFirstOrNull() }
-    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
-    actual abstract fun finishPrepare(prepareOp: PrepareOp)
-    protected abstract fun onComplete()
-
-    actual open fun onPrepare(prepareOp: PrepareOp): Any? {
-        finishPrepare(prepareOp)
-        return null
-    }
-
-    actual open fun onRemoved(affected: Node) {}
-
-    actual final override fun prepare(op: AtomicOp<*>): Any? {
-        val affected = affectedNode
-        val failure = failure(affected)
-        if (failure != null) return failure
-        @Suppress("UNCHECKED_CAST")
-        return onPrepare(PrepareOp(affected, this, op))
-    }
-
-    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 actual class PrepareOp(
-    actual val affected: LockFreeLinkedListNode,
-    actual val desc: AbstractAtomicDesc,
-    actual override val atomicOp: AtomicOp<*>
-): OpDescriptor() {
-    override fun perform(affected: Any?): Any? = null
-    actual fun finishPrepare() {}
-}
-
-/** @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/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
index dcbb202..edbd3fd 100644
--- a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
@@ -5,16 +5,16 @@
 package kotlinx.coroutines.internal
 
 import kotlinx.coroutines.*
+import kotlinx.atomicfu.locks.withLock as withLock2
 
 /**
  * @suppress **This an internal API and should not be used from general code.**
  */
 @InternalCoroutinesApi
-public actual typealias SynchronizedObject = Any
+public actual typealias SynchronizedObject = kotlinx.atomicfu.locks.SynchronizedObject
 
 /**
  * @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()
+public actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T = lock.withLock2(block)
diff --git a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt
new file mode 100644
index 0000000..639b5fb
--- /dev/null
+++ b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.internal.*
+import platform.posix.*
+import kotlin.native.concurrent.*
+import kotlin.random.*
+
+actual fun randomWait() {
+    val n = Random.nextInt(1000)
+    if (n < 500) return // no wait 50% of time
+    repeat(n) {
+        BlackHole.sink *= 3
+    }
+    // use the BlackHole value somehow, so even if the compiler gets smarter, it won't remove the object
+    val sinkValue = if (BlackHole.sink > 16) 1 else 0
+    if (n + sinkValue > 900) sched_yield()
+}
+
+@ThreadLocal
+private object BlackHole {
+    var sink = 1
+}
+
+internal actual typealias SuppressSupportingThrowable = SuppressSupportingThrowableImpl
+
+actual val Throwable.suppressed: Array<Throwable>
+    get() = (this as? SuppressSupportingThrowableImpl)?.suppressed ?: emptyArray()
+
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+actual fun Throwable.printStackTrace() = printStackTrace()
+
+actual fun currentThreadName(): String = Worker.current.name
diff --git a/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt b/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt
index a39a59e..b040993 100644
--- a/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt
+++ b/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt
@@ -8,22 +8,6 @@
 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 {
diff --git a/kotlinx-coroutines-core/native/test/RunBlockingTest.kt b/kotlinx-coroutines-core/native/test/RunBlockingTest.kt
deleted file mode 100644
index c5d08af..0000000
--- a/kotlinx-coroutines-core/native/test/RunBlockingTest.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines
-import kotlin.test.*
-
-class RunBlockingTest : TestBase() {
-
-    @Test
-    fun testIncompleteState() {
-        val handle = runBlocking {
-            coroutineContext[Job]!!.invokeOnCompletion { }
-        }
-
-        handle.dispose()
-    }
-}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt
index 4ffa6c0..6fef475 100644
--- a/kotlinx-coroutines-core/native/test/TestBase.kt
+++ b/kotlinx-coroutines-core/native/test/TestBase.kt
@@ -4,16 +4,21 @@
 
 package kotlinx.coroutines
 
+import kotlinx.atomicfu.*
+
 public actual val isStressTest: Boolean = false
 public actual val stressTestMultiplier: Int = 1
+public actual val stressTestMultiplierSqrt: Int = 1
+
+public actual val isNative = true
 
 @Suppress("ACTUAL_WITHOUT_EXPECT")
 public actual typealias TestResult = Unit
 
 public actual open class TestBase actual constructor() {
     public actual val isBoundByJsTestTimeout = false
-    private var actionIndex = 0
-    private var finished = false
+    private var actionIndex = atomic(0)
+    private var finished = atomic(false)
     private var error: Throwable? = null
 
     /**
@@ -36,7 +41,7 @@
      * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
      */
     public actual fun expect(index: Int) {
-        val wasIndex = ++actionIndex
+        val wasIndex = actionIndex.incrementAndGet()
         check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
     }
 
@@ -52,21 +57,21 @@
      */
     public actual fun finish(index: Int) {
         expect(index)
-        check(!finished) { "Should call 'finish(...)' at most once" }
-        finished = true
+        check(!finished.value) { "Should call 'finish(...)' at most once" }
+        finished.value = true
     }
 
     /**
      * Asserts that [finish] was invoked
      */
-    public actual fun ensureFinished() {
-        require(finished) { "finish(...) should be caller prior to this check" }
+    actual fun ensureFinished() {
+        require(finished.value) { "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
+    actual fun reset() {
+        check(actionIndex.value == 0 || finished.value) { "Expecting that 'finish(...)' was invoked, but it was not" }
+        actionIndex.value = 0
+        finished.value = false
     }
 
     @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
@@ -78,7 +83,7 @@
         var exCount = 0
         var ex: Throwable? = null
         try {
-            runBlocking(block = block, context = CoroutineExceptionHandler { context, e ->
+            runBlocking(block = block, context = CoroutineExceptionHandler { _, e ->
                 if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
                 exCount++
                 when {
diff --git a/kotlinx-coroutines-core/native/test/TestBaseExtension.kt b/kotlinx-coroutines-core/native/test/TestBaseExtension.kt
new file mode 100644
index 0000000..fde2cde
--- /dev/null
+++ b/kotlinx-coroutines-core/native/test/TestBaseExtension.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+
+actual fun TestBase.runMtTest(
+    expected: ((Throwable) -> Boolean)?,
+    unhandled: List<(Throwable) -> Boolean>,
+    block: suspend CoroutineScope.() -> Unit
+) {
+    if (!multithreadingSupported) return
+    return runTest(expected, unhandled, block)
+}
diff --git a/kotlinx-coroutines-core/native/test/WorkerTest.kt b/kotlinx-coroutines-core/native/test/WorkerTest.kt
index d6b5fad..8252ca6 100644
--- a/kotlinx-coroutines-core/native/test/WorkerTest.kt
+++ b/kotlinx-coroutines-core/native/test/WorkerTest.kt
@@ -23,12 +23,12 @@
     }
 
     @Test
-    fun testLaunchInWorkerTroughGlobalScope() {
+    fun testLaunchInWorkerThroughGlobalScope() {
         val worker = Worker.start()
         worker.execute(TransferMode.SAFE, { }) {
             runBlocking {
                 CoroutineScope(EmptyCoroutineContext).launch {
-                    delay(1)
+                    delay(10)
                 }.join()
             }
         }.result
diff --git a/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt b/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt
index 6c1fddf..44ddf47 100644
--- a/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt
+++ b/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt
@@ -10,11 +10,11 @@
 import kotlin.test.assertTrue
 
 class LinkedListTest {
-    data class IntNode(val i: Int) : LinkedListNode()
+    data class IntNode(val i: Int) : LockFreeLinkedListNode()
 
     @Test
     fun testSimpleAddLastRemove() {
-        val list = LinkedListHead()
+        val list = LockFreeLinkedListHead()
         assertContents(list)
         val n1 = IntNode(1).apply { list.addLast(this) }
         assertContents(list, 1)
@@ -35,7 +35,7 @@
         assertContents(list)
     }
 
-    private fun assertContents(list: LinkedListHead, vararg expected: Int) {
+    private fun assertContents(list: LockFreeLinkedListHead, vararg expected: Int) {
         val n = expected.size
         val actual = IntArray(n)
         var index = 0
diff --git a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt
new file mode 100644
index 0000000..ace2042
--- /dev/null
+++ b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.cinterop.*
+import kotlinx.coroutines.internal.*
+import platform.CoreFoundation.*
+import platform.darwin.*
+import kotlin.coroutines.*
+import kotlin.native.concurrent.*
+import kotlin.native.internal.NativePtr
+
+internal fun isMainThread(): Boolean = CFRunLoopGetCurrent() == CFRunLoopGetMain()
+
+internal actual fun createMainDispatcher(default: CoroutineDispatcher): MainCoroutineDispatcher =
+    if (multithreadingSupported) DarwinMainDispatcher(false) else OldMainDispatcher(Dispatchers.Default)
+
+internal actual fun createDefaultDispatcher(): CoroutineDispatcher = DarwinGlobalQueueDispatcher
+
+private object DarwinGlobalQueueDispatcher : CoroutineDispatcher() {
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        autoreleasepool {
+            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.convert(), 0)) {
+                block.run()
+            }
+        }
+    }
+}
+
+private class DarwinMainDispatcher(
+    private val invokeImmediately: Boolean
+) : MainCoroutineDispatcher(), Delay {
+    
+    override val immediate: MainCoroutineDispatcher =
+        if (invokeImmediately) this else DarwinMainDispatcher(true)
+
+    override fun isDispatchNeeded(context: CoroutineContext): Boolean = !(invokeImmediately && isMainThread())
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        autoreleasepool {
+            dispatch_async(dispatch_get_main_queue()) {
+                block.run()
+            }
+        }
+    }
+    
+    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+        val timer = Timer()
+        val timerBlock: TimerBlock = {
+            timer.dispose()
+            continuation.resume(Unit)
+        }
+        timer.start(timeMillis, timerBlock)
+        continuation.disposeOnCancellation(timer)
+    }
+
+    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
+        val timer = Timer()
+        val timerBlock: TimerBlock = {
+            timer.dispose()
+            block.run()
+        }
+        timer.start(timeMillis, timerBlock)
+        return timer
+    }
+
+    override fun toString(): String =
+        "MainDispatcher${ if(invokeImmediately) "[immediate]" else "" }"
+}
+
+private typealias TimerBlock = (CFRunLoopTimerRef?) -> Unit
+
+private val TIMER_NEW = NativePtr.NULL
+private val TIMER_DISPOSED = NativePtr.NULL.plus(1)
+
+private class Timer : DisposableHandle {
+    private val ref = AtomicNativePtr(TIMER_NEW)
+
+    fun start(timeMillis: Long, timerBlock: TimerBlock) {
+        val fireDate = CFAbsoluteTimeGetCurrent() + timeMillis / 1000.0
+        val timer = CFRunLoopTimerCreateWithHandler(null, fireDate, 0.0, 0u, 0, timerBlock)
+        CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes)
+        if (!ref.compareAndSet(TIMER_NEW, timer.rawValue)) {
+            // dispose was already called concurrently
+            release(timer)
+        }
+    }
+
+    override fun dispose() {
+        while (true) {
+            val ptr = ref.value
+            if (ptr == TIMER_DISPOSED) return
+            if (ref.compareAndSet(ptr, TIMER_DISPOSED)) {
+                if (ptr != TIMER_NEW) release(interpretCPointer(ptr))
+                return
+            }
+        }
+    }
+
+    private fun release(timer: CFRunLoopTimerRef?) {
+        CFRunLoopRemoveTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes)
+        CFRelease(timer)
+    }
+}
+
+internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit): Unit = autoreleasepool { block() }
diff --git a/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt b/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt
new file mode 100644
index 0000000..d460bd6
--- /dev/null
+++ b/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+import platform.CoreFoundation.*
+import platform.darwin.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class MainDispatcherTest : TestBase() {
+
+    private fun isMainThread(): Boolean = CFRunLoopGetCurrent() == CFRunLoopGetMain()
+    private fun canTestMainDispatcher() = !isMainThread() && multithreadingSupported
+
+    private fun runTestNotOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) {
+        // skip if already on the main thread, run blocking doesn't really work well with that
+        if (!canTestMainDispatcher()) return
+        runTest(block = block)
+    }
+
+    @Test
+    fun testDispatchNecessityCheckWithMainImmediateDispatcher() = runTestNotOnMainDispatcher {
+        val main = Dispatchers.Main.immediate
+        assertTrue(main.isDispatchNeeded(EmptyCoroutineContext))
+        withContext(Dispatchers.Default) {
+            assertTrue(main.isDispatchNeeded(EmptyCoroutineContext))
+            withContext(Dispatchers.Main) {
+                assertFalse(main.isDispatchNeeded(EmptyCoroutineContext))
+            }
+            assertTrue(main.isDispatchNeeded(EmptyCoroutineContext))
+        }
+    }
+
+    @Test
+    fun testWithContext() = runTestNotOnMainDispatcher {
+        expect(1)
+        assertFalse(isMainThread())
+        withContext(Dispatchers.Main) {
+            assertTrue(isMainThread())
+            expect(2)
+        }
+        assertFalse(isMainThread())
+        finish(3)
+    }
+
+    @Test
+    fun testWithContextDelay() = runTestNotOnMainDispatcher {
+        expect(1)
+        withContext(Dispatchers.Main) {
+            assertTrue(isMainThread())
+            expect(2)
+            delay(100)
+            assertTrue(isMainThread())
+            expect(3)
+        }
+        assertFalse(isMainThread())
+        finish(4)
+    }
+
+    @Test
+    fun testWithTimeoutContextDelayNoTimeout() = runTestNotOnMainDispatcher {
+        expect(1)
+        withTimeout(1000) {
+            withContext(Dispatchers.Main) {
+                assertTrue(isMainThread())
+                expect(2)
+                delay(100)
+                assertTrue(isMainThread())
+                expect(3)
+            }
+        }
+        assertFalse(isMainThread())
+        finish(4)
+    }
+
+    @Test
+    fun testWithTimeoutContextDelayTimeout() = runTestNotOnMainDispatcher {
+        expect(1)
+         assertFailsWith<TimeoutCancellationException> {
+            withTimeout(100) {
+                withContext(Dispatchers.Main) {
+                    assertTrue(isMainThread())
+                    expect(2)
+                    delay(1000)
+                    expectUnreached()
+                }
+            }
+            expectUnreached()
+        }
+        assertFalse(isMainThread())
+        finish(3)
+    }
+
+    @Test
+    fun testWithContextTimeoutDelayNoTimeout() = runTestNotOnMainDispatcher {
+        expect(1)
+        withContext(Dispatchers.Main) {
+            withTimeout(1000) {
+                assertTrue(isMainThread())
+                expect(2)
+                delay(100)
+                assertTrue(isMainThread())
+                expect(3)
+            }
+        }
+        assertFalse(isMainThread())
+        finish(4)
+    }
+
+    @Test
+    fun testWithContextTimeoutDelayTimeout() = runTestNotOnMainDispatcher {
+        expect(1)
+        assertFailsWith<TimeoutCancellationException> {
+            withContext(Dispatchers.Main) {
+                withTimeout(100) {
+                    assertTrue(isMainThread())
+                    expect(2)
+                    delay(1000)
+                    expectUnreached()
+                }
+            }
+            expectUnreached()
+        }
+        assertFalse(isMainThread())
+        finish(3)
+    }
+}
diff --git a/kotlinx-coroutines-core/nativeOther/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeOther/src/Dispatchers.kt
new file mode 100644
index 0000000..517190d
--- /dev/null
+++ b/kotlinx-coroutines-core/nativeOther/src/Dispatchers.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+internal actual fun createMainDispatcher(default: CoroutineDispatcher): MainCoroutineDispatcher =
+    MissingMainDispatcher
+
+internal actual fun createDefaultDispatcher(): CoroutineDispatcher = DefaultDispatcher
+
+private object DefaultDispatcher : CoroutineDispatcher() {
+
+    // Delegated, so users won't be able to downcast and call 'close'
+    // The precise number of threads cannot be obtained until KT-48179 is implemented, 4 is just "good enough" number.
+    private val ctx = newFixedThreadPoolContext(4, "Dispatchers.Default")
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        ctx.dispatch(context, block)
+    }
+}
+
+private object MissingMainDispatcher : MainCoroutineDispatcher() {
+    override val immediate: MainCoroutineDispatcher
+        get() = notImplemented()
+    override fun dispatch(context: CoroutineContext, block: Runnable) = notImplemented()
+    override fun isDispatchNeeded(context: CoroutineContext): Boolean = notImplemented()
+    override fun dispatchYield(context: CoroutineContext, block: Runnable) = notImplemented()
+
+    private fun notImplemented(): Nothing = TODO("Dispatchers.Main is missing on the current platform")
+}
+
+internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit) = block()
diff --git a/kotlinx-coroutines-core/npm/README.md b/kotlinx-coroutines-core/npm/README.md
index 7f88ea3..868fb65 100644
--- a/kotlinx-coroutines-core/npm/README.md
+++ b/kotlinx-coroutines-core/npm/README.md
@@ -16,4 +16,4 @@
 ## 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)
+* [Full kotlinx.coroutines API reference](https://kotlinlang.org/api/kotlinx.coroutines/)
diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md
index cd71f58..5f302d2 100644
--- a/kotlinx-coroutines-debug/README.md
+++ b/kotlinx-coroutines-debug/README.md
@@ -61,7 +61,7 @@
 ### Using as JVM agent
 
 Debug module can also be used as a standalone JVM agent to enable debug probes on the application startup.
-You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.5.2.jar`.
+You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.6.4.jar`.
 Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines.
 When used as Java agent, `"kotlinx.coroutines.debug.enable.creation.stack.trace"` system property can be used to control 
 [DebugProbes.enableCreationStackTraces] along with agent startup.
@@ -154,9 +154,8 @@
 
 ### 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 
+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;`,
+and it is not possible to use coroutine debugger along with Android emulator.
 
 <!---
 Make an exception googlable
@@ -265,22 +264,22 @@
 <!--- 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
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[CoroutineScope]: https://kotlinlang.org/api/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
-[DebugProbes.enableCreationStackTraces]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/enable-creation-stack-traces.html
+[DebugProbes]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/index.html
+[DebugProbes.install]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/install.html
+[DebugProbes.dumpCoroutines]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines.html
+[DebugProbes.dumpCoroutinesInfo]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines-info.html
+[DebugProbes.printJob]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-job.html
+[DebugProbes.printScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-scope.html
+[DebugProbes.enableCreationStackTraces]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/enable-creation-stack-traces.html
 
 <!--- INDEX kotlinx.coroutines.debug.junit4 -->
 
-[CoroutinesTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug.junit4/-coroutines-timeout/index.html
+[CoroutinesTimeout]: https://kotlinlang.org/api/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
index 43d94d1..9165a41 100644
--- a/kotlinx-coroutines-debug/build.gradle
+++ b/kotlinx-coroutines-debug/build.gradle
@@ -8,25 +8,18 @@
     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(api) // shadow - resulting configuration with shaded jar file
-    configureKotlinJvmPlatform(shadow)
 }
 
 dependencies {
     compileOnly "junit:junit:$junit_version"
     compileOnly "org.junit.jupiter:junit-jupiter-api:$junit5_version"
-    testCompile "org.junit.jupiter:junit-jupiter-engine:$junit5_version"
-    testCompile "org.junit.platform:junit-platform-testkit:1.7.0"
+    testImplementation "org.junit.jupiter:junit-jupiter-engine:$junit5_version"
+    testImplementation "org.junit.platform:junit-platform-testkit:1.7.0"
     shadowDeps "net.bytebuddy:byte-buddy:$byte_buddy_version"
     shadowDeps "net.bytebuddy:byte-buddy-agent:$byte_buddy_version"
     compileOnly "io.projectreactor.tools:blockhound:$blockhound_version"
     testImplementation "io.projectreactor.tools:blockhound:$blockhound_version"
+    testImplementation "com.google.code.gson:gson:2.8.6"
     api "net.java.dev.jna:jna:$jna_version"
     api "net.java.dev.jna:jna-platform:$jna_version"
 }
@@ -38,15 +31,48 @@
 }
 
 jar {
+    setEnabled(false)
+}
+
+// This is a rough estimation of what shadow plugin has been doing with our default configuration prior to
+// 1.6.2: https://github.com/johnrengelman/shadow/blob/1ff12fc816629ae5bc331fa3889c8ecfcaee7b27/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy#L72-L82
+// We just emulate it here for backwards compatibility
+shadowJar.configure {
+    def classpath = project.objects.fileCollection().from { ->
+        project.configurations.findByName('runtimeClasspath')
+    }
+    doFirst {
+        manifest.attributes 'Class-Path': classpath.collect { "${it.name}" }.findAll { it }.join(' ')
+    }
+}
+
+def shadowJarTask = shadowJar {
+    classifier null
+    // Shadow only byte buddy, do not package kotlin stdlib
+    configurations = [project.configurations.shadowDeps]
+    relocate('net.bytebuddy', 'kotlinx.coroutines.repackaged.net.bytebuddy')
+
     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')
+configurations {
+    artifacts {
+        add("apiElements", shadowJarTask)
+        add("runtimeElements", shadowJarTask)
+    }
+}
+
+def commonKoverExcludes =
+        // Never used, safety mechanism
+        ["kotlinx.coroutines.debug.internal.NoOpProbesKt"]
+
+tasks.koverHtmlReport {
+    excludes = commonKoverExcludes
+}
+
+tasks.koverVerify {
+    excludes = commonKoverExcludes
 }
diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt
index 190476c..a26c192 100644
--- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt
+++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt
@@ -10,7 +10,6 @@
 import reactor.blockhound.*
 import reactor.blockhound.integration.*
 
-@Suppress("UNUSED")
 public class CoroutinesBlockHoundIntegration : BlockHoundIntegration {
 
     override fun applyTo(builder: BlockHound.Builder): Unit = with(builder) {
@@ -19,6 +18,9 @@
         allowServiceLoaderInvocationsOnInit()
         allowBlockingCallsInReflectionImpl()
         allowBlockingCallsInDebugProbes()
+        allowBlockingCallsInWorkQueue()
+        // Stacktrace recovery cache is guarded by lock
+        allowBlockingCallsInside("kotlinx.coroutines.internal.ExceptionsConstructorKt", "tryCopyException")
         /* The predicates that define that BlockHound should only report blocking calls from threads that are part of
         the coroutine thread pool and currently execute a CPU-bound coroutine computation. */
         addDynamicThreadPredicate { isSchedulerWorker(it) }
@@ -61,15 +63,20 @@
     }
 
     /**
+     * Allow blocking calls inside [kotlinx.coroutines.scheduling.WorkQueue]
+     */
+    private fun BlockHound.Builder.allowBlockingCallsInWorkQueue() {
+        /** uses [Thread.yield] in a benign way. */
+        allowBlockingCallsInside("kotlinx.coroutines.scheduling.WorkQueue", "addLast")
+    }
+
+    /**
      * Allows blocking inside [kotlinx.coroutines.internal.ThreadSafeHeap].
      */
     private fun BlockHound.Builder.allowBlockingCallsInThreadSafeHeap() {
         for (method in listOf("clear", "peek", "removeFirstOrNull", "addLast")) {
             allowBlockingCallsInside("kotlinx.coroutines.internal.ThreadSafeHeap", method)
         }
-        // [addLastIf] is only used in [EventLoop.common]. Users of [removeFirstIf]:
-        allowBlockingCallsInside("kotlinx.coroutines.test.TestCoroutineDispatcher", "doActionsUntil")
-        allowBlockingCallsInside("kotlinx.coroutines.test.TestCoroutineContext", "triggerActions")
     }
 
     private fun BlockHound.Builder.allowBlockingCallsInFlow() {
@@ -110,7 +117,7 @@
     private fun BlockHound.Builder.allowBlockingCallsInArrayChannel() {
         for (method in listOf(
             "pollInternal", "isEmpty", "isFull", "isClosedForReceive", "offerInternal", "offerSelectInternal",
-            "enqueueSend", "pollInternal", "pollSelectInternal", "enqueueReceiveInternal", "onCancelIdempotent"))
+            "enqueueSend", "pollSelectInternal", "enqueueReceiveInternal", "onCancelIdempotent"))
         {
             allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayChannel", method)
         }
@@ -133,7 +140,7 @@
      */
     private fun BlockHound.Builder.allowBlockingCallsInConflatedChannel() {
         for (method in listOf("offerInternal", "offerSelectInternal", "pollInternal", "pollSelectInternal",
-            "onCancelIdempotent"))
+            "onCancelIdempotent", "isEmpty", "enqueueReceiveInternal"))
         {
             allowBlockingCallsInside("kotlinx.coroutines.channels.ConflatedChannel", method)
         }
diff --git a/kotlinx-coroutines-debug/test/BlockHoundTest.kt b/kotlinx-coroutines-debug/test/BlockHoundTest.kt
index 571daca..3f58878 100644
--- a/kotlinx-coroutines-debug/test/BlockHoundTest.kt
+++ b/kotlinx-coroutines-debug/test/BlockHoundTest.kt
@@ -4,6 +4,7 @@
 import org.junit.*
 import reactor.blockhound.*
 
+@Suppress("UnusedEquals", "DeferredResultUnused", "BlockingMethodInNonBlockingContext")
 class BlockHoundTest : TestBase() {
 
     @Before
@@ -12,21 +13,21 @@
     }
 
     @Test(expected = BlockingOperationError::class)
-    fun shouldDetectBlockingInDefault() = runTest {
+    fun testShouldDetectBlockingInDefault() = runTest {
         withContext(Dispatchers.Default) {
             Thread.sleep(1)
         }
     }
 
     @Test
-    fun shouldNotDetectBlockingInIO() = runTest {
+    fun testShouldNotDetectBlockingInIO() = runTest {
         withContext(Dispatchers.IO) {
             Thread.sleep(1)
         }
     }
 
     @Test
-    fun shouldNotDetectNonblocking() = runTest {
+    fun testShouldNotDetectNonblocking() = runTest {
         withContext(Dispatchers.Default) {
             val a = 1
             val b = 2
@@ -54,7 +55,7 @@
     }
 
     @Test
-    fun testChannelsNotBeingConsideredBlocking() = runTest {
+    fun testChannelNotBeingConsideredBlocking() = runTest {
         withContext(Dispatchers.Default) {
             // Copy of kotlinx.coroutines.channels.ArrayChannelTest.testSimple
             val q = Channel<Int>(1)
@@ -74,6 +75,24 @@
         }
     }
 
+    @Test
+    fun testConflatedChannelsNotBeingConsideredBlocking() = runTest {
+        withContext(Dispatchers.Default) {
+            val q = Channel<Int>(Channel.CONFLATED)
+            check(q.isEmpty)
+            check(!q.isClosedForReceive)
+            check(!q.isClosedForSend)
+            val sender = launch {
+                q.send(1)
+            }
+            val receiver = launch {
+                q.receive() == 1
+            }
+            sender.join()
+            receiver.join()
+        }
+    }
+
     @Test(expected = BlockingOperationError::class)
     fun testReusingThreadsFailure() = runTest {
         val n = 100
diff --git a/kotlinx-coroutines-debug/test/DumpCoroutineInfoAsJsonAndReferencesTest.kt b/kotlinx-coroutines-debug/test/DumpCoroutineInfoAsJsonAndReferencesTest.kt
new file mode 100644
index 0000000..4808470
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/DumpCoroutineInfoAsJsonAndReferencesTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+package kotlinx.coroutines.debug
+
+import com.google.gson.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.internal.*
+import org.junit.Test
+import kotlin.coroutines.*
+import kotlin.test.*
+
+@ExperimentalStdlibApi
+class DumpCoroutineInfoAsJsonAndReferencesTest : DebugTestBase() {
+    private data class CoroutineInfoFromJson(
+        val name: String?,
+        val id: Long?,
+        val dispatcher: String?,
+        val sequenceNumber: Long?,
+        val state: String?
+    )
+
+    @Test
+    fun testDumpOfUnnamedCoroutine() =
+        runTestWithNamedDeferred(name = null)
+
+    @Test
+    fun testDumpOfNamedCoroutine() =
+        runTestWithNamedDeferred("Name")
+
+    @Test
+    fun testDumpWithNoCoroutines() {
+        val dumpResult = DebugProbesImpl.dumpCoroutinesInfoAsJsonAndReferences()
+        assertEquals(dumpResult.size, 4)
+        assertIsEmptyArray(dumpResult[1])
+        assertIsEmptyArray(dumpResult[2])
+        assertIsEmptyArray(dumpResult[3])
+    }
+
+    private fun assertIsEmptyArray(obj: Any) =
+        assertTrue(obj is Array<*> && obj.isEmpty())
+
+    private fun runTestWithNamedDeferred(name: String?) = runTest {
+        val context = if (name == null) EmptyCoroutineContext else CoroutineName(name)
+        val deferred = async(context) {
+            suspendingMethod()
+            assertTrue(true)
+        }
+        yield()
+        verifyDump()
+        deferred.cancelAndJoin()
+    }
+
+    private suspend fun suspendingMethod() {
+        delay(Long.MAX_VALUE)
+    }
+
+    private fun verifyDump() {
+        val dumpResult = DebugProbesImpl.dumpCoroutinesInfoAsJsonAndReferences()
+
+        assertEquals(dumpResult.size, 4)
+
+        val coroutinesInfoAsJsonString = dumpResult[0]
+        val lastObservedThreads = dumpResult[1]
+        val lastObservedFrames = dumpResult[2]
+        val coroutinesInfo = dumpResult[3]
+
+        assertTrue(coroutinesInfoAsJsonString is String)
+        assertTrue(lastObservedThreads is Array<*>)
+        assertTrue(lastObservedFrames is Array<*>)
+        assertTrue(coroutinesInfo is Array<*>)
+
+        val coroutinesInfoFromJson = Gson().fromJson(coroutinesInfoAsJsonString, Array<CoroutineInfoFromJson>::class.java)
+
+        val size = coroutinesInfo.size
+        assertTrue(size != 0)
+        assertEquals(size, coroutinesInfoFromJson.size)
+        assertEquals(size, lastObservedFrames.size)
+        assertEquals(size, lastObservedThreads.size)
+
+        for (i in 0 until size) {
+            val info = coroutinesInfo[i]
+            val infoFromJson = coroutinesInfoFromJson[i]
+            assertTrue(info is DebugCoroutineInfo)
+            assertEquals(info.lastObservedThread, lastObservedThreads[i])
+            assertEquals(info.lastObservedFrame, lastObservedFrames[i])
+            assertEquals(info.sequenceNumber, infoFromJson.sequenceNumber)
+            assertEquals(info.state, infoFromJson.state)
+            val context = info.context
+            assertEquals(context[CoroutineName.Key]?.name, infoFromJson.name)
+            assertEquals(context[CoroutineId.Key]?.id, infoFromJson.id)
+            assertEquals(context[CoroutineDispatcher.Key]?.toString(), infoFromJson.dispatcher)
+        }
+    }
+}
diff --git a/kotlinx-coroutines-debug/test/EnhanceStackTraceWithTreadDumpAsJsonTest.kt b/kotlinx-coroutines-debug/test/EnhanceStackTraceWithTreadDumpAsJsonTest.kt
new file mode 100644
index 0000000..fcf9f1a
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/EnhanceStackTraceWithTreadDumpAsJsonTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+package kotlinx.coroutines.debug
+
+import com.google.gson.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.debug.internal.*
+import org.junit.Test
+import kotlin.test.*
+
+class EnhanceStackTraceWithTreadDumpAsJsonTest : DebugTestBase() {
+    private data class StackTraceElementInfoFromJson(
+        val declaringClass: String,
+        val methodName: String,
+        val fileName: String?,
+        val lineNumber: Int
+    )
+
+    @Test
+    fun testEnhancedStackTraceFormatWithDeferred() = runTest {
+        val deferred = async {
+            suspendingMethod()
+            assertTrue(true)
+        }
+        yield()
+
+        val coroutineInfo = DebugProbesImpl.dumpCoroutinesInfo()
+        assertEquals(coroutineInfo.size, 2)
+        val info = coroutineInfo[1]
+        val enhancedStackTraceAsJsonString = DebugProbesImpl.enhanceStackTraceWithThreadDumpAsJson(info)
+        val enhancedStackTraceFromJson = Gson().fromJson(enhancedStackTraceAsJsonString, Array<StackTraceElementInfoFromJson>::class.java)
+        val enhancedStackTrace = DebugProbesImpl.enhanceStackTraceWithThreadDump(info, info.lastObservedStackTrace)
+        assertEquals(enhancedStackTrace.size, enhancedStackTraceFromJson.size)
+        for ((frame, frameFromJson) in enhancedStackTrace.zip(enhancedStackTraceFromJson)) {
+            assertEquals(frame.className, frameFromJson.declaringClass)
+            assertEquals(frame.methodName, frameFromJson.methodName)
+            assertEquals(frame.fileName, frameFromJson.fileName)
+            assertEquals(frame.lineNumber, frameFromJson.lineNumber)
+        }
+
+        deferred.cancelAndJoin()
+    }
+
+    private suspend fun suspendingMethod() {
+        delay(Long.MAX_VALUE)
+    }
+}
diff --git a/kotlinx-coroutines-debug/test/StacktraceUtils.kt b/kotlinx-coroutines-debug/test/StacktraceUtils.kt
index 8c591eb..9cc626f 100644
--- a/kotlinx-coroutines-debug/test/StacktraceUtils.kt
+++ b/kotlinx-coroutines-debug/test/StacktraceUtils.kt
@@ -90,7 +90,8 @@
 public fun verifyDump(vararg traces: String, ignoredCoroutine: String? = null) {
     val baos = ByteArrayOutputStream()
     DebugProbes.dumpCoroutines(PrintStream(baos))
-    val trace = baos.toString().split("\n\n")
+    val wholeDump = baos.toString()
+    val trace = wholeDump.split("\n\n")
     if (traces.isEmpty()) {
         val filtered = trace.filter { ignoredCoroutine == null || !it.contains(ignoredCoroutine) }
         assertEquals(1, filtered.count())
@@ -105,7 +106,7 @@
 
         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, "Creation stacktrace should be part of the expected input")
+        assertEquals(expected.size, actual.size, "Creation stacktrace should be part of the expected input. Whole dump:\n$wholeDump")
 
         expected.withIndex().forEach { (index, trace) ->
             val actualTrace = actual[index].trimStackTrace().sanitizeAddresses()
@@ -113,7 +114,7 @@
             val actualLines = cleanBlockHoundTraces(actualTrace.split("\n"))
             val expectedLines = expectedTrace.split("\n")
             for (i in expectedLines.indices) {
-                assertEquals(expectedLines[i], actualLines[i])
+                assertEquals(expectedLines[i], actualLines[i], "Whole dump:\n$wholeDump")
             }
         }
     }
diff --git a/kotlinx-coroutines-debug/test/ToStringTest.kt b/kotlinx-coroutines-debug/test/ToStringTest.kt
index 0a9e84e..0ea412b 100644
--- a/kotlinx-coroutines-debug/test/ToStringTest.kt
+++ b/kotlinx-coroutines-debug/test/ToStringTest.kt
@@ -8,6 +8,7 @@
 import kotlinx.coroutines.channels.*
 import org.junit.*
 import org.junit.Test
+import java.io.*
 import kotlin.coroutines.*
 import kotlin.test.*
 
@@ -105,6 +106,8 @@
         expect(6)
         assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage())
         assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).trimEnd().trimStackTrace().trimPackage())
+        assertEquals(expected, printToString { DebugProbes.printScope(CoroutineScope(root), it) }.trimEnd().trimStackTrace().trimPackage())
+        assertEquals(expected, printToString { DebugProbes.printJob(root, it) }.trimEnd().trimStackTrace().trimPackage())
 
         root.cancelAndJoin()
         finish(7)
@@ -145,4 +148,12 @@
             }
         }
     }
+
+    private inline fun printToString(block: (PrintStream) -> Unit): String {
+        val baos = ByteArrayOutputStream()
+        val ps = PrintStream(baos)
+        block(ps)
+        ps.close()
+        return baos.toString()
+    }
 }
diff --git a/kotlinx-coroutines-test/MIGRATION.md b/kotlinx-coroutines-test/MIGRATION.md
new file mode 100644
index 0000000..10c197f
--- /dev/null
+++ b/kotlinx-coroutines-test/MIGRATION.md
@@ -0,0 +1,447 @@
+# Migration to the new kotlinx-coroutines-test API
+
+In version 1.6.0, the API of the test module changed significantly.
+This is a guide for gradually adapting the existing test code to the new API.
+This guide is written step-by-step; the idea is to separate the migration into several sets of small changes.
+
+## Remove custom `UncaughtExceptionCaptor`, `DelayController`, and `TestCoroutineScope` implementations
+
+We couldn't find any code that defined new implementations of these interfaces, so they are deprecated. It's likely that
+you don't need to do anything for this section.
+
+### `UncaughtExceptionCaptor`
+
+If the code base has an `UncaughtExceptionCaptor`, its special behavior as opposed to just `CoroutineExceptionHandler`
+was that, at the end of `runBlockingTest` or `cleanupTestCoroutines` (or both), its `cleanupTestCoroutines` procedure
+was called.
+
+We currently don't provide a replacement for this.
+However, `runTest` follows structured concurrency better than `runBlockingTest` did, so exceptions from child coroutines
+are propagated structurally, which makes uncaught exception handlers less useful.
+
+If you have a use case for this, please tell us about it at the issue tracker.
+Meanwhile, it should be possible to use a custom exception captor, which should only implement
+`CoroutineExceptionHandler` now, like this:
+
+```kotlin
+@Test
+fun testFoo() = runTest {
+    val customCaptor = MyUncaughtExceptionCaptor()
+    launch(customCaptor) {
+        // ...
+    }
+    advanceUntilIdle()
+    customCaptor.cleanupTestCoroutines()
+}
+```
+
+### `DelayController`
+
+We don't provide a way to define custom dispatching strategies that support virtual time.
+That said, we significantly enhanced this mechanism:
+* Using multiple test dispatchers simultaneously is supported.
+  For the dispatchers to have a shared knowledge of the virtual time, either the same `TestCoroutineScheduler` should be
+  passed to each of them, or all of them should be constructed after `Dispatchers.setMain` is called with some test
+  dispatcher.
+* Both a simple `StandardTestDispatcher` that is always paused, and unconfined `UnconfinedTestDispatcher` are provided.
+
+If you have a use case for `DelayController` that's not covered by what we provide, please tell us about it in the issue
+tracker.
+
+### `TestCoroutineScope`
+
+This scope couldn't be meaningfully used in tandem with `runBlockingTest`: according to the definition of
+`TestCoroutineScope.runBlockingTest`, only the scope's `coroutineContext` is used.
+So, there could be two reasons for defining a custom implementation:
+
+* Avoiding the restrictions on placed `coroutineContext` in the `TestCoroutineScope` constructor function.
+  These restrictions consisted of requirements for `CoroutineExceptionHandler` being an `UncaughtExceptionCaptor`, and
+  `ContinuationInterceptor` being a `DelayController`, so it is also possible to fulfill these restrictions by defining
+  conforming instances. In this case, follow the instructions about replacing them.
+* Using without `runBlockingTest`. In this case, you don't even need to implement `TestCoroutineScope`: nothing else
+  accepts a `TestCoroutineScope` specifically as an argument.
+
+## Remove usages of `TestCoroutineExceptionHandler` and `TestCoroutineScope.uncaughtExceptions`
+
+It is already illegal to use a `TestCoroutineScope` without performing `cleanupTestCoroutines`, so the valid uses of
+`TestCoroutineExceptionHandler` include:
+
+* Accessing `uncaughtExceptions` in the middle of the test to make sure that there weren't any uncaught exceptions
+  *yet*.
+  If there are any, they will be thrown by the cleanup procedure anyway.
+  We don't support this use case, given how comparatively rare it is, but it can be handled in the same way as the
+  following one.
+* Accessing `uncaughtExceptions` when the uncaught exceptions are actually expected.
+  In this case, `cleanupTestCoroutines` will fail with an exception that is being caught later.
+  It would be better in this case to use a custom `CoroutineExceptionHandler` so that actual problems that could be
+  found by the cleanup procedure are not superseded by the exceptions that are expected.
+  An example is shown below.
+
+```kotlin
+val exceptions = mutableListOf<Throwable>()
+val customCaptor = CoroutineExceptionHandler { ctx, throwable ->
+    exceptions.add(throwable) // add proper synchronization if the test is multithreaded
+}
+
+@Test
+fun testFoo() = runTest {
+    launch(customCaptor) {
+        // ...
+    }
+    advanceUntilIdle()
+    // check the list of the caught exceptions
+}
+```
+
+## Auto-replace `TestCoroutineScope` constructor function with `createTestCoroutineScope`
+
+This should not break anything, as `TestCoroutineScope` is now defined in terms of `createTestCoroutineScope`.
+If it does break something, it means that you already supplied a `TestCoroutineScheduler` to some scope; in this case,
+also pass this scheduler as the argument to the dispatcher.
+
+## Replace usages of `pauseDispatcher` and `resumeDispatcher` with a `StandardTestDispatcher`
+
+* In places where `pauseDispatcher` in its block form is called, replace it with a call to
+  `withContext(StandardTestDispatcher(testScheduler))`
+  (`testScheduler` is available as a field of `TestCoroutineScope`,
+  or `scheduler` is available as a field of `TestCoroutineDispatcher`),
+  followed by `advanceUntilIdle()`.
+  This is not an automatic replacement, as there can be tricky situations where the test dispatcher is already paused
+  when `pauseDispatcher { X }` is called. In such cases, simply replace `pauseDispatcher { X }` with `X`.
+* Often, `pauseDispatcher()` in a non-block form is used at the start of the test.
+  Then, attempt to remove `TestCoroutineDispatcher` from the arguments to `createTestCoroutineScope`,
+  if a standalone `TestCoroutineScope` or the `scope.runBlockingTest` form is used,
+  or pass a `StandardTestDispatcher` as an argument to `runBlockingTest`.
+  This will lead to the test using a `StandardTestDispatcher`, which does not allow pausing and resuming,
+  instead of the deprecated `TestCoroutineDispatcher`.
+* Sometimes, `pauseDispatcher()` and `resumeDispatcher()` are employed used throughout the test.
+  In this case, attempt to wrap everything until the next `resumeDispatcher()` in
+  a `withContext(StandardTestDispatcher(testScheduler))` block, or try using some other combinations of
+  `StandardTestDispatcher` (where dispatches are needed) and `UnconfinedTestDispatcher` (where it isn't important where
+  execution happens).
+
+## Replace `advanceTimeBy(n)` with `advanceTimeBy(n); runCurrent()`
+
+For `TestCoroutineScope` and `DelayController`, the `advanceTimeBy` method is deprecated.
+It is not deprecated for `TestCoroutineScheduler` and `TestScope`, but has a different meaning: it does not run the
+tasks scheduled *at* `currentTime + n`.
+
+There is an automatic replacement for this deprecation, which produces correct but inelegant code.
+
+Alternatively, you can wait until replacing `TestCoroutineScope` with `TestScope`: it's possible that you will not
+encounter this edge case.
+
+## Replace `runBlockingTest` with `runTest(UnconfinedTestDispatcher())`
+
+This is a major change, affecting many things, and can be done in parallel with replacing `TestCoroutineScope` with
+`TestScope`.
+
+Significant differences of `runTest` from `runBlockingTest` are each given a section below.
+
+### It works properly with other dispatchers and asynchronous completions.
+
+No action on your part is required, other than replacing `runBlocking` with `runTest` as well.
+
+### It uses `StandardTestDispatcher` by default, not `TestCoroutineDispatcher`.
+
+By now, calls to `pauseDispatcher` and `resumeDispatcher` should be purged from the code base, so only the unpaused
+variant of `TestCoroutineDispatcher` should be used.
+This version of the dispatcher has the property of eagerly entering `launch` and `async` blocks:
+code until the first suspension is executed without dispatching.
+
+There are two common ways in which this property is useful.
+
+#### `TestCoroutineDispatcher` for the top-level coroutine
+
+Some tests that rely on `launch` and `async` blocks being entered immediately have a form similar to this:
+```kotlin
+runTest(TestCoroutineDispatcher()) {
+    launch {
+        updateSomething()
+    }
+    checkThatSomethingWasUpdated()
+    launch {
+        updateSomethingElse()
+    }
+    checkThatSomethingElseWasUpdated()
+}
+```
+
+If the `TestCoroutineDispatcher()` is simply removed, `StandardTestDispatcher()` will be used, which will cause
+the test to fail.
+
+In these cases, `UnconfinedTestDispatcher()` should be used.
+We ensured that, when run with an `UnconfinedTestDispatcher`, `runTest` also eagerly enters `launch` and `async`
+blocks.
+
+Note though that *this only works at the top level*: if a child coroutine also called `launch` or `async`, we don't provide
+any guarantees about their dispatching order.
+
+#### `TestCoroutineDispatcher` for testing intermediate emissions
+
+Some code tests `StateFlow` or channels in a manner similar to this:
+
+```kotlin
+@Test
+fun testAllEmissions() = runTest(TestCoroutineDispatcher()) {
+    val values = mutableListOf<Int>()
+    val stateFlow = MutableStateFlow(0)
+    val job = launch {
+        stateFlow.collect {
+            values.add(it)
+        }
+    }
+    stateFlow.value = 1
+    stateFlow.value = 2
+    stateFlow.value = 3
+    job.cancel()
+    // each assignment will immediately resume the collecting child coroutine,
+    // so no values will be skipped.
+    assertEquals(listOf(0, 1, 2, 3), values)
+}
+```
+
+Such code will fail when `TestCoroutineDispatcher()` is not used: not every emission will be listed.
+In this particular case, none will be listed at all.
+
+The reason for this is that setting `stateFlow.value` (as is sending to a channel, as are some other things) wakes up
+the coroutine waiting for the new value, but *typically* does not immediately run the collecting code, instead simply
+dispatching it.
+The exceptions are the coroutines running in dispatchers that don't (always) go through a dispatch,
+`Dispatchers.Unconfined`, `Dispatchers.Main.immediate`, `UnconfinedTestDispatcher`, or `TestCoroutineDispatcher` in
+the unpaused state.
+
+Therefore, a solution is to launch the collection in an unconfined dispatcher:
+
+```kotlin
+@Test
+fun testAllEmissions() = runTest {
+    val values = mutableListOf<Int>()
+    val stateFlow = MutableStateFlow(0)
+    val job = launch(UnconfinedTestDispatcher(testScheduler)) { // <------
+        stateFlow.collect {
+            values.add(it)
+        }
+    }
+    stateFlow.value = 1
+    stateFlow.value = 2
+    stateFlow.value = 3
+    job.cancel()
+    // each assignment will immediately resume the collecting child coroutine,
+    // so no values will be skipped.
+    assertEquals(listOf(0, 1, 2, 3), values)
+}
+```
+
+Note that `testScheduler` is passed so that the unconfined dispatcher is linked to `runTest`.
+Also, note that `UnconfinedTestDispatcher` is not passed to `runTest`.
+This is due to the fact that, *inside* the `UnconfinedTestDispatcher`, there are no execution order guarantees,
+so it would not be guaranteed that setting `stateFlow.value` would immediately run the collecting code
+(though in this case, it does).
+
+#### Other considerations
+
+Using `UnconfinedTestDispatcher` as an argument to `runTest` will probably lead to the test being executed as it
+did, but it's still possible that the test relies on the specific dispatching order of `TestCoroutineDispatcher`,
+so it will need to be tweaked.
+
+If some code is expected to have run at some point, but it hasn't, use `runCurrent` to force the tasks scheduled
+at this moment of time to run.
+For example, the `StateFlow` example above can also be forced to succeed by doing this:
+
+```kotlin
+@Test
+fun testAllEmissions() = runTest {
+    val values = mutableListOf<Int>()
+    val stateFlow = MutableStateFlow(0)
+    val job = launch {
+        stateFlow.collect {
+            values.add(it)
+        }
+    }
+    runCurrent()
+    stateFlow.value = 1
+    runCurrent()
+    stateFlow.value = 2
+    runCurrent()
+    stateFlow.value = 3
+    runCurrent()
+    job.cancel()
+    // each assignment will immediately resume the collecting child coroutine,
+    // so no values will be skipped.
+    assertEquals(listOf(0, 1, 2, 3), values)
+}
+```
+
+Be wary though of this approach: using `runCurrent`, `advanceTimeBy`, or `advanceUntilIdle` is, essentially,
+simulating some particular execution order, which is not guaranteed to happen in production code.
+For example, using `UnconfinedTestDispatcher` to fix this test reflects how, in production code, one could use
+`Dispatchers.Unconfined` to observe all emitted values without conflation, but the `runCurrent()` approach only
+states that the behavior would be observed if a dispatch were to happen at some chosen points.
+It is, therefore, recommended to structure tests in a way that does not rely on a particular interleaving, unless
+that is the intention.
+
+### The job hierarchy is completely different.
+
+- Structured concurrency is used, with the scope provided as the receiver of `runTest` actually being the scope of the
+  created coroutine.
+- Not `SupervisorJob` but a normal `Job` is used for the `TestCoroutineScope`.
+- The job passed as an argument is used as a parent job.
+
+Most tests should not be affected by this. In case your test is, try explicitly launching a child coroutine with a
+`SupervisorJob`; this should make the job hierarchy resemble what it used to be.
+
+```kotlin
+@Test
+fun testFoo() = runTest {
+    val deferred = async(SupervisorJob()) {
+        // test code
+    }
+    advanceUntilIdle()
+    deferred.getCompletionExceptionOrNull()?.let {
+      throw it
+    }
+}
+```
+
+### Only a single call to `runTest` is permitted per test.
+
+In order to work on JS, only a single call to `runTest` must happen during one test, and its result must be returned
+immediately:
+
+```kotlin
+@Test
+fun testFoo(): TestResult {
+    // arbitrary code here
+    return runTest {
+        // ...
+    }
+}
+```
+
+When used only on the JVM, `runTest` will work when called repeatedly, but this is not supported.
+Please only call `runTest` once per test, and if for some reason you can't, please tell us about in on the issue
+tracker.
+
+### It uses `TestScope`, not `TestCoroutineScope`, by default.
+
+There is a `runTestWithLegacyScope` method that allows migrating from `runBlockingTest` to `runTest` before migrating
+from `TestCoroutineScope` to `TestScope`, if exactly the `TestCoroutineScope` needs to be passed somewhere else and
+`TestScope` will not suffice.
+
+## Replace `TestCoroutineScope.cleanupTestCoroutines` with `runTest`
+
+Likely can be done together with the next step.
+
+Remove all calls to `TestCoroutineScope.cleanupTestCoroutines` from the code base.
+Instead, as the last step of each test, do `return scope.runTest`; if possible, the whole test body should go inside
+the `runTest` block.
+
+The cleanup procedure in `runTest` will not check that the virtual time doesn't advance during cleanup.
+If a test must check that no other delays are remaining after it has finished, the following form may help:
+```kotlin
+runTest {
+    testBody()
+    val timeAfterTest = currentTime()
+    advanceUntilIdle() // run the remaining tasks
+    assertEquals(timeAfterTest, currentTime()) // will fail if there were tasks scheduled at a later moment
+}
+```
+Note that this will report time advancement even if the job scheduled at a later point was cancelled.
+
+It may be the case that `cleanupTestCoroutines` must be executed after de-initialization in `@AfterTest`, which happens
+outside the test itself.
+In this case, we propose that you write a wrapper of the form:
+
+```kotlin
+fun runTestAndCleanup(body: TestScope.() -> Unit) = runTest {
+    try {
+        body()
+    } finally {
+        // the usual cleanup procedures that used to happen before `cleanupTestCoroutines`
+    }
+}
+```
+
+## Replace `runBlockingTest` with `runBlockingTestOnTestScope`, `createTestCoroutineScope` with `TestScope`
+
+Also, replace `runTestWithLegacyScope` with just `runTest`.
+All of this can be done in parallel with replacing `runBlockingTest` with `runTest`.
+
+This step should remove all uses of `TestCoroutineScope`, explicit or implicit.
+
+Replacing `runTestWithLegacyScope` and `runBlockingTest` with `runTest` and `runBlockingTestOnTestScope` should be
+straightforward if there is no more code left that requires passing exactly `TestCoroutineScope` to it.
+Some tests may fail because `TestCoroutineScope.cleanupTestCoroutines` and the cleanup procedure in `runTest`
+handle cancelled tasks differently: if there are *cancelled* jobs pending at the moment of
+`TestCoroutineScope.cleanupTestCoroutines`, they are ignored, whereas `runTest` will report them.
+
+Of all the methods supported by `TestCoroutineScope`, only `cleanupTestCoroutines` is not provided on `TestScope`,
+and its usages should have been removed during the previous step.
+
+## Replace `runBlocking` with `runTest`
+
+Now that `runTest` works properly with asynchronous completions, `runBlocking` is only occasionally useful.
+As is, most uses of `runBlocking` in tests come from the need to interact with dispatchers that execute on other
+threads, like `Dispatchers.IO` or `Dispatchers.Default`.
+
+## Replace `TestCoroutineDispatcher` with `UnconfinedTestDispatcher` and `StandardTestDispatcher`
+
+`TestCoroutineDispatcher` is a dispatcher with two modes:
+* ("unpaused") Almost (but not quite) unconfined, with the ability to eagerly enter `launch` and `async` blocks.
+* ("paused") Behaving like a `StandardTestDispatcher`.
+
+In one of the earlier steps, we replaced `pauseDispatcher` with `StandardTestDispatcher` usage, and replaced the
+implicit `TestCoroutineScope` dispatcher in `runBlockingTest` with `UnconfinedTestDispatcher` during migration to
+`runTest`.
+
+Now, the rest of the usages should be replaced with whichever dispatcher is most appropriate.
+
+## Simplify code by removing unneeded entities
+
+Likely, now some code has the form
+
+```kotlin
+val dispatcher = StandardTestDispatcher()
+val scope = TestScope(dispatcher)
+
+@BeforeTest
+fun setUp() {
+    Dispatchers.setMain(dispatcher)
+}
+
+@AfterTest
+fun tearDown() {
+    Dispatchers.resetMain()
+}
+
+@Test
+fun testFoo() = scope.runTest {
+    // ...
+}
+```
+
+The point of this pattern is to ensure that the test runs with the same `TestCoroutineScheduler` as the one used for
+`Dispatchers.Main`.
+
+However, now this can be simplified to just
+
+```kotlin
+@BeforeTest
+fun setUp() {
+  Dispatchers.setMain(StandardTestDispatcher())
+}
+
+@AfterTest
+fun tearDown() {
+  Dispatchers.resetMain()
+}
+
+@Test
+fun testFoo() = runTest {
+  // ...
+}
+```
+
+The reason this works is that all entities that depend on `TestCoroutineScheduler` will attempt to acquire one from
+the current `Dispatchers.Main`.
diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md
index 43ae18f..f45ccd0 100644
--- a/kotlinx-coroutines-test/README.md
+++ b/kotlinx-coroutines-test/README.md
@@ -2,35 +2,54 @@
 
 Test utilities for `kotlinx.coroutines`.
 
-This package provides testing utilities for effectively testing coroutines.
+## Overview
+
+This package provides utilities for efficiently testing coroutines.
+
+| Name | Description |
+| ---- | ----------- |
+| [runTest] | Runs the test code, automatically skipping delays and handling uncaught exceptions. |
+| [TestCoroutineScheduler] | The shared source of virtual time, used for controlling execution order and skipping delays. |
+| [TestScope] | A [CoroutineScope] that integrates with [runTest], providing access to [TestCoroutineScheduler]. |
+| [TestDispatcher] | A [CoroutineDispatcher] whose delays are controlled by a [TestCoroutineScheduler]. |
+| [Dispatchers.setMain] | Mocks the main dispatcher using the provided one. If mocked with a [TestDispatcher], its [TestCoroutineScheduler] is used everywhere by default. |
+
+Provided [TestDispatcher] implementations:
+
+| Name | Description |
+| ---- | ----------- |
+| [StandardTestDispatcher] | A simple dispatcher with no special behavior other than being linked to a [TestCoroutineScheduler]. |
+| [UnconfinedTestDispatcher] | A dispatcher that behaves like [Dispatchers.Unconfined]. |
 
 ## Using in your project
 
 Add `kotlinx-coroutines-test` to your project test dependencies:
 ```
 dependencies {
-    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
+    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
 }
 ```
 
-**Do not** depend on this project in your main sources, all utilities are intended and designed to be used only from tests.
+**Do not** depend on this project in your main sources, all utilities here 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.
+`Dispatchers.setMain` will override the `Main` dispatcher in test scenarios.
+This is helpful when one wants to execute a test in situations where the platform `Main` dispatcher is not available,
+or 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.
+On the JVM,
+the [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) mechanism is responsible
+for overwriting [Dispatchers.Main] with a testable implementation, which by default will delegate its calls to the real
+`Main` dispatcher, if any.
 
-You can override the `Main` implementation using [setMain][setMain] method with any [CoroutineDispatcher] implementation, e.g.:
+The `Main` implementation can be overridden using [Dispatchers.setMain][setMain] method with any [CoroutineDispatcher]
+implementation, e.g.:
 
 ```kotlin
 
 class SomeTest {
-    
+
     private val mainThreadSurrogate = newSingleThreadContext("UI thread")
 
     @Before
@@ -40,10 +59,10 @@
 
     @After
     fun tearDown() {
-        Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
+        Dispatchers.resetMain() // reset the main dispatcher to the original Main dispatcher
         mainThreadSurrogate.close()
     }
-    
+
     @Test
     fun testSomeUI() = runBlocking {
         launch(Dispatchers.Main) {  // Will be launched in the mainThreadSurrogate dispatcher
@@ -52,372 +71,289 @@
     }
 }
 ```
-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
+Calling `setMain` or `resetMain` immediately changes the `Main` dispatcher globally.
 
-To test regular suspend functions or coroutines started with `launch` or `async` use the [runBlockingTest] coroutine 
-builder that provides extra test control to coroutines. 
+If `Main` is overridden with a [TestDispatcher], then its [TestCoroutineScheduler] is used when new [TestDispatcher] or
+[TestScope] instances are created without [TestCoroutineScheduler] being passed as an argument.
 
-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
+## runTest
 
-### Testing regular suspend functions
+[runTest] is the way to test code that involves coroutines. `suspend` functions can be called inside it.
 
-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.
+**IMPORTANT: in order to work with on Kotlin/JS, the result of `runTest` must be immediately `return`-ed from each test.**
+The typical invocation of [runTest] thus looks like this:
 
 ```kotlin
 @Test
-fun testFoo() = runBlockingTest { // a coroutine with an extra test control
-    val actual = foo() 
+fun testFoo() = runTest {
+    // code under test
+}
+```
+
+In more advanced scenarios, it's possible instead to use the following form:
+```kotlin
+@Test
+fun testFoo(): TestResult {
+    // initialize some test state
+    return runTest {
+        // code under test
+    }
+}
+```
+
+[runTest] is similar to running the code with `runBlocking` on Kotlin/JVM and Kotlin/Native, or launching a new promise
+on Kotlin/JS. The main differences are the following:
+
+* **The calls to `delay` are automatically skipped**, preserving the relative execution order of the tasks. This way,
+  it's possible to make tests finish more-or-less immediately.
+* **Controlling the virtual time**: in case just skipping delays is not sufficient, it's possible to more carefully
+  guide the execution, advancing the virtual time by a duration, draining the queue of the awaiting tasks, or running
+  the tasks scheduled at the present moment.
+* **Handling uncaught exceptions** spawned in the child coroutines by throwing them at the end of the test.
+* **Waiting for asynchronous callbacks**.
+  Sometimes, especially when working with third-party code, it's impossible to mock all the dispatchers in use.
+  [runTest] will handle the situations where some code runs in dispatchers not integrated with the test module.
+
+## Delay-skipping
+
+To test regular suspend functions, which may have a delay, just run them inside the [runTest] block.
+
+```kotlin
+@Test
+fun testFoo() = runTest { // 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
+    delay(1_000) // when run in `runTest`, will finish immediately instead of delaying
     // ...
 }
 ```
 
-`runBlockingTest` returns `Unit` so it may be used in a single expression with common testing libraries.
+## `launch` and `async`
 
-### Testing `launch` or `async`
+The coroutine dispatcher used for tests is single-threaded, meaning that the child coroutines of the [runTest] block
+will run on the thread that started the test, and will never run in parallel.
 
-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].
+If several coroutines are waiting to be executed next, the one scheduled after the smallest delay will be chosen.
+The virtual time will automatically advance to the point of its resumption.
 
 ```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() {
+fun testWithMultipleDelays() = runTest {
     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)
+        delay(1_000)
+        println("1. $currentTime") // 1000
+        delay(200)
+        println("2. $currentTime") // 1200
+        delay(2_000)
+        println("4. $currentTime") // 3200
     }
+    val deferred = async {
+        delay(3_000)
+        println("3. $currentTime") // 3000
+        delay(500)
+        println("5. $currentTime") // 3500
+    }
+    deferred.await()
 }
 ```
 
-*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.
+## Controlling the virtual time
 
-### 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.
+Inside [runTest], the following operations are supported:
+* `currentTime` gets the current virtual time.
+* `runCurrent()` runs the tasks that are scheduled at this point of virtual time.
+* `advanceUntilIdle()` runs all enqueued tasks until there are no more.
+* `advanceTimeBy(timeDelta)` runs the enqueued tasks until the current virtual time advances by `timeDelta`.
 
 ```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
-    // ...
+@Test
+fun testFoo() = runTest {
+    launch {
+        println(1)   // executes during runCurrent()
+        delay(1_000) // suspends until time is advanced by at least 1_000
+        println(2)   // executes during advanceTimeBy(2_000)
+        delay(500)   // suspends until the time is advanced by another 500 ms
+        println(3)   // also executes during advanceTimeBy(2_000)
+        delay(5_000) // will suspend by another 4_500 ms
+        println(4)   // executes during advanceUntilIdle()
+    }
+    // the child coroutine has not run yet
+    runCurrent()
+    // the child coroutine has called println(1), and is suspended on delay(1_000)
+    advanceTimeBy(2_000) // progress time, this will cause two calls to `delay` to resume
+    // the child coroutine has called println(2) and println(3) and suspends for another 4_500 virtual milliseconds
+    advanceUntilIdle() // will run the child coroutine to completion
+    assertEquals(6500, currentTime) // the child coroutine finished at virtual time of 6_500 milliseconds
+}
+```
+
+## Using multiple test dispatchers
+
+The virtual time is controlled by an entity called the [TestCoroutineScheduler], which behaves as the shared source of
+virtual time.
+
+Several dispatchers can be created that use the same [TestCoroutineScheduler], in which case they will share their
+knowledge of the virtual time.
+
+To access the scheduler used for this test, use the [TestScope.testScheduler] property.
+
+```kotlin
+@Test
+fun testWithMultipleDispatchers() = runTest {
+        val scheduler = testScheduler // the scheduler used for this test
+        val dispatcher1 = StandardTestDispatcher(scheduler, name = "IO dispatcher")
+        val dispatcher2 = StandardTestDispatcher(scheduler, name = "Background dispatcher")
+        launch(dispatcher1) {
+            delay(1_000)
+            println("1. $currentTime") // 1000
+            delay(200)
+            println("2. $currentTime") // 1200
+            delay(2_000)
+            println("4. $currentTime") // 3200
+        }
+        val deferred = async(dispatcher2) {
+            delay(3_000)
+            println("3. $currentTime") // 3000
+            delay(500)
+            println("5. $currentTime") // 3500
+        }
+        deferred.await()
+    }
+```
+
+**Note: if [Dispatchers.Main] is replaced by a [TestDispatcher], [runTest] will automatically use its scheduler.
+This is done so that there is no need to go through the ceremony of passing the correct scheduler to [runTest].**
+
+## Accessing the test coroutine scope
+
+Structured concurrency ties coroutines to scopes in which they are launched.
+[TestScope] is a special coroutine scope designed for testing coroutines, and a new one is automatically created
+for [runTest] and used as the receiver for the test body.
+
+However, it can be convenient to access a `CoroutineScope` before the test has started, for example, to perform mocking
+of some
+parts of the system in `@BeforeTest` via dependency injection.
+In these cases, it is possible to manually create [TestScope], the scope for the test coroutines, in advance,
+before the test begins.
+
+[TestScope] on its own does not automatically run the code launched in it.
+In addition, it is stateful in order to keep track of executing coroutines and uncaught exceptions.
+Therefore, it is important to ensure that [TestScope.runTest] is called eventually.
+
+```kotlin
+val scope = TestScope()
+
+@BeforeTest
+fun setUp() {
+    Dispatchers.setMain(StandardTestDispatcher(scope.testScheduler))
+    TestSubject.setScope(scope)
 }
 
-fun CoroutineScope.foo(resultDeferred: Deferred<Foo>) {
+@AfterTest
+fun tearDown() {
+    Dispatchers.resetMain()
+    TestSubject.resetScope()
+}
+
+@Test
+fun testSubject() = scope.runTest {
+    // the receiver here is `testScope`
+}
+```
+
+## Eagerly entering `launch` and `async` blocks
+
+Some tests only test functionality and don't particularly care about the precise order in which coroutines are
+dispatched.
+In these cases, it can be cumbersome to always call [runCurrent] or [yield] to observe the effects of the coroutines
+after they are launched.
+
+If [runTest] executes with an [UnconfinedTestDispatcher], the child coroutines launched at the top level are entered
+*eagerly*, that is, they don't go through a dispatch until the first suspension.
+
+```kotlin
+@Test
+fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
+    var entered = false
+    val deferred = CompletableDeferred<Unit>()
+    var completed = false
     launch {
+        entered = true
+        deferred.await()
+        completed = true
+    }
+    assertTrue(entered) // `entered = true` already executed.
+    assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued.
+    deferred.complete(Unit) // resume the coroutine.
+    assertTrue(completed) // now the child coroutine is immediately completed.
+}
+```
+
+If this behavior is desirable, but some parts of the test still require accurate dispatching, for example, to ensure
+that the code executes on the correct thread, then simply `launch` a new coroutine with the [StandardTestDispatcher].
+
+```kotlin
+@Test
+fun testEagerlyEnteringSomeChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
+    var entered1 = false
+    launch {
+        entered1 = true
+    }
+    assertTrue(entered1) // `entered1 = true` already executed
+
+    var entered2 = false
+    launch(StandardTestDispatcher(testScheduler)) {
+        // this block and every coroutine launched inside it will explicitly go through the needed dispatches
+        entered2 = true
+    }
+    assertFalse(entered2)
+    runCurrent() // need to explicitly run the dispatched continuation
+    assertTrue(entered2)
+}
+```
+
+### Using `withTimeout` inside `runTest`
+
+Timeouts are also susceptible to time control, so the code below will immediately finish.
+
+```kotlin
+@Test
+fun testFooWithTimeout() = runTest {
+    assertFailsWith<TimeoutCancellationException> {
         withTimeout(1_000) {
-            resultDeferred.await() // await() will suspend forever waiting for uncompleted
-            // ...
+            delay(999)
+            delay(2)
+            println("this won't be reached")
         }
     }
 }
 ```
 
-*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.
+## Virtual time support with other dispatchers
 
-### 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.
+Calls to `withContext(Dispatchers.IO)`, `withContext(Dispatchers.Default)` ,and `withContext(Dispatchers.Main)` are
+common in coroutines-based code bases. Unfortunately, just executing code in a test will not lead to these dispatchers
+using the virtual time source, so delays will not be skipped in them.
 
 ```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
-    
-    @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) {
+suspend fun veryExpensiveFunction() = withContext(Dispatchers.Default) {
     delay(1_000)
-    1 // for very expensive values of 1
+    1
+}
+
+fun testExpensiveFunction() = runTest {
+    val result = veryExpensiveFunction() // will take a whole real-time second to execute
+    // the virtual time at this point is still 0
 }
 ```
 
-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.
-
+Tests should, when possible, replace these dispatchers with a [TestDispatcher] if the `withContext` calls `delay` in the
+function under test. For example, `veryExpensiveFunction` above should allow mocking with a [TestDispatcher] using
+either dependency injection, a service locator, or a default parameter, if it is to be used with virtual time.
 
 ### Status of the API
 
@@ -426,36 +362,32 @@
 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 
+If you have any suggestions for improvements to this experimental API please share 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
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[CoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+[Dispatchers.Unconfined]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
+[Dispatchers.Main]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
+[yield]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
+[ExperimentalCoroutinesApi]: https://kotlinlang.org/api/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/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
+[runTest]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html
+[TestCoroutineScheduler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scheduler/index.html
+[TestScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/index.html
+[TestDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-dispatcher/index.html
+[Dispatchers.setMain]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html
+[StandardTestDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-standard-test-dispatcher.html
+[UnconfinedTestDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-unconfined-test-dispatcher.html
+[setMain]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html
+[TestScope.testScheduler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/test-scheduler.html
+[TestScope.runTest]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html
+[runCurrent]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-current.html
 
 <!--- END -->
diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
index c99ec5c..bf63923 100644
--- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
+++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
@@ -13,27 +13,45 @@
 	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 final fun runBlockingTest (Lkotlinx/coroutines/test/TestScope;Lkotlin/jvm/functions/Function2;)V
 	public static synthetic fun runBlockingTest$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+	public static final fun runBlockingTestOnTestScope (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)V
+	public static synthetic fun runBlockingTestOnTestScope$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+	public static final fun runTest (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;)V
+	public static final fun runTest (Lkotlinx/coroutines/test/TestCoroutineScope;JLkotlin/jvm/functions/Function2;)V
+	public static final fun runTest (Lkotlinx/coroutines/test/TestScope;JLkotlin/jvm/functions/Function2;)V
+	public static synthetic fun runTest$default (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+	public static synthetic fun runTest$default (Lkotlinx/coroutines/test/TestCoroutineScope;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+	public static synthetic fun runTest$default (Lkotlinx/coroutines/test/TestScope;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+	public static final fun runTestWithLegacyScope (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;)V
+	public static synthetic fun runTestWithLegacyScope$default (Lkotlin/coroutines/CoroutineContext;JLkotlin/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 final class kotlinx/coroutines/test/TestCoroutineDispatcher : kotlinx/coroutines/test/TestDispatcher, kotlinx/coroutines/Delay, kotlinx/coroutines/test/SchedulerAsDelayController {
 	public fun <init> ()V
+	public fun <init> (Lkotlinx/coroutines/test/TestCoroutineScheduler;)V
+	public synthetic fun <init> (Lkotlinx/coroutines/test/TestCoroutineScheduler;ILkotlin/jvm/internal/DefaultConstructorMarker;)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;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/DisposableHandle;
+	public fun getScheduler ()Lkotlinx/coroutines/test/TestCoroutineScheduler;
 	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/TestCoroutineDispatchersKt {
+	public static final fun StandardTestDispatcher (Lkotlinx/coroutines/test/TestCoroutineScheduler;Ljava/lang/String;)Lkotlinx/coroutines/test/TestDispatcher;
+	public static synthetic fun StandardTestDispatcher$default (Lkotlinx/coroutines/test/TestCoroutineScheduler;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/test/TestDispatcher;
+	public static final fun UnconfinedTestDispatcher (Lkotlinx/coroutines/test/TestCoroutineScheduler;Ljava/lang/String;)Lkotlinx/coroutines/test/TestDispatcher;
+	public static synthetic fun UnconfinedTestDispatcher$default (Lkotlinx/coroutines/test/TestCoroutineScheduler;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/test/TestDispatcher;
+}
+
 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
@@ -41,13 +59,44 @@
 	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 final class kotlinx/coroutines/test/TestCoroutineScheduler : kotlin/coroutines/AbstractCoroutineContextElement, kotlin/coroutines/CoroutineContext$Element {
+	public static final field Key Lkotlinx/coroutines/test/TestCoroutineScheduler$Key;
+	public fun <init> ()V
+	public final fun advanceTimeBy (J)V
+	public final fun advanceUntilIdle ()V
+	public final fun getCurrentTime ()J
+	public final fun getTimeSource ()Lkotlin/time/TimeSource;
+	public final fun runCurrent ()V
+}
+
+public final class kotlinx/coroutines/test/TestCoroutineScheduler$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
+public abstract interface class kotlinx/coroutines/test/TestCoroutineScope : kotlinx/coroutines/CoroutineScope {
 	public abstract fun cleanupTestCoroutines ()V
+	public abstract fun getTestScheduler ()Lkotlinx/coroutines/test/TestCoroutineScheduler;
 }
 
 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 static final fun advanceTimeBy (Lkotlinx/coroutines/test/TestCoroutineScope;J)V
+	public static final fun advanceUntilIdle (Lkotlinx/coroutines/test/TestCoroutineScope;)V
+	public static final fun createTestCoroutineScope (Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/test/TestCoroutineScope;
+	public static synthetic fun createTestCoroutineScope$default (Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/test/TestCoroutineScope;
+	public static final fun getCurrentTime (Lkotlinx/coroutines/test/TestCoroutineScope;)J
+	public static final fun getUncaughtExceptions (Lkotlinx/coroutines/test/TestCoroutineScope;)Ljava/util/List;
+	public static final fun pauseDispatcher (Lkotlinx/coroutines/test/TestCoroutineScope;)V
+	public static final fun pauseDispatcher (Lkotlinx/coroutines/test/TestCoroutineScope;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+	public static final fun resumeDispatcher (Lkotlinx/coroutines/test/TestCoroutineScope;)V
+	public static final fun runCurrent (Lkotlinx/coroutines/test/TestCoroutineScope;)V
+}
+
+public abstract class kotlinx/coroutines/test/TestDispatcher : kotlinx/coroutines/CoroutineDispatcher, kotlinx/coroutines/Delay {
+	public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+	public abstract fun getScheduler ()Lkotlinx/coroutines/test/TestCoroutineScheduler;
+	public fun invokeOnTimeout (JLjava/lang/Runnable;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/DisposableHandle;
+	public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/CancellableContinuation;)V
 }
 
 public final class kotlinx/coroutines/test/TestDispatchers {
@@ -55,13 +104,23 @@
 	public static final fun setMain (Lkotlinx/coroutines/Dispatchers;Lkotlinx/coroutines/CoroutineDispatcher;)V
 }
 
+public abstract interface class kotlinx/coroutines/test/TestScope : kotlinx/coroutines/CoroutineScope {
+	public abstract fun getBackgroundScope ()Lkotlinx/coroutines/CoroutineScope;
+	public abstract fun getTestScheduler ()Lkotlinx/coroutines/test/TestCoroutineScheduler;
+}
+
+public final class kotlinx/coroutines/test/TestScopeKt {
+	public static final fun TestScope (Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/test/TestScope;
+	public static synthetic fun TestScope$default (Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/test/TestScope;
+	public static final fun advanceTimeBy (Lkotlinx/coroutines/test/TestScope;J)V
+	public static final fun advanceUntilIdle (Lkotlinx/coroutines/test/TestScope;)V
+	public static final fun getCurrentTime (Lkotlinx/coroutines/test/TestScope;)J
+	public static final fun getTestTimeSource (Lkotlinx/coroutines/test/TestScope;)Lkotlin/time/TimeSource;
+	public static final fun runCurrent (Lkotlinx/coroutines/test/TestScope;)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/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts
index fef0a14..7b244bb 100644
--- a/kotlinx-coroutines-test/build.gradle.kts
+++ b/kotlinx-coroutines-test/build.gradle.kts
@@ -2,6 +2,12 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
-dependencies {
-    implementation(project(":kotlinx-coroutines-debug"))
+val experimentalAnnotations = listOf(
+    "kotlin.Experimental",
+    "kotlinx.coroutines.ExperimentalCoroutinesApi",
+    "kotlinx.coroutines.InternalCoroutinesApi"
+)
+
+kotlin {
+    sourceSets.all { configureMultiplatform() }
 }
diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt
new file mode 100644
index 0000000..80dc8d6
--- /dev/null
+++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:JvmName("TestBuildersKt")
+@file:JvmMultifileClass
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * A test result.
+ *
+ * * On JVM and Native, this resolves to [Unit], representing the fact that tests are run in a blocking manner on these
+ *   platforms: a call to a function returning a [TestResult] will simply execute the test inside it.
+ * * On JS, this is a `Promise`, which reflects the fact that the test-running function does not wait for a test to
+ *   finish. The JS test frameworks typically support returning `Promise` from a test and will correctly handle it.
+ *
+ * Because of the behavior on JS, extra care must be taken when writing multiplatform tests to avoid losing test errors:
+ * * Don't do anything after running the functions returning a [TestResult]. On JS, this code will execute *before* the
+ *   test finishes.
+ * * As a corollary, don't run functions returning a [TestResult] more than once per test. The only valid thing to do
+ *   with a [TestResult] is to immediately `return` it from a test.
+ * * Don't nest functions returning a [TestResult].
+ */
+@Suppress("NO_ACTUAL_FOR_EXPECT")
+@ExperimentalCoroutinesApi
+public expect class TestResult
+
+/**
+ * Executes [testBody] as a test in a new coroutine, returning [TestResult].
+ *
+ * On JVM and Native, this function behaves similarly to `runBlocking`, with the difference that the code that it runs
+ * will skip delays. This allows to use [delay] in without causing the tests to take more time than necessary.
+ * On JS, this function creates a `Promise` that executes the test body with the delay-skipping behavior.
+ *
+ * ```
+ * @Test
+ * fun exampleTest() = runTest {
+ *     val deferred = async {
+ *         delay(1_000)
+ *         async {
+ *             delay(1_000)
+ *         }.await()
+ *     }
+ *
+ *     deferred.await() // result available immediately
+ * }
+ * ```
+ *
+ * The platform difference entails that, in order to use this function correctly in common code, one must always
+ * immediately return the produced [TestResult] from the test method, without doing anything else afterwards. See
+ * [TestResult] for details on this.
+ *
+ * The test is run in a single thread, unless other [CoroutineDispatcher] are used for child coroutines.
+ * Because of this, child coroutines are not executed in parallel to the test body.
+ * In order to for the spawned-off asynchronous code to actually be executed, one must either [yield] or suspend the
+ * test body some other way, or use commands that control scheduling (see [TestCoroutineScheduler]).
+ *
+ * ```
+ * @Test
+ * fun exampleWaitingForAsyncTasks1() = runTest {
+ *     // 1
+ *     val job = launch {
+ *         // 3
+ *     }
+ *     // 2
+ *     job.join() // the main test coroutine suspends here, so the child is executed
+ *     // 4
+ * }
+ *
+ * @Test
+ * fun exampleWaitingForAsyncTasks2() = runTest {
+ *     // 1
+ *     launch {
+ *         // 3
+ *     }
+ *     // 2
+ *     advanceUntilIdle() // runs the tasks until their queue is empty
+ *     // 4
+ * }
+ * ```
+ *
+ * ### Task scheduling
+ *
+ * Delay-skipping is achieved by using virtual time.
+ * If [Dispatchers.Main] is set to a [TestDispatcher] via [Dispatchers.setMain] before the test,
+ * then its [TestCoroutineScheduler] is used;
+ * otherwise, a new one is automatically created (or taken from [context] in some way) and can be used to control
+ * the virtual time, advancing it, running the tasks scheduled at a specific time etc.
+ * Some convenience methods are available on [TestScope] to control the scheduler.
+ *
+ * Delays in code that runs inside dispatchers that don't use a [TestCoroutineScheduler] don't get skipped:
+ * ```
+ * @Test
+ * fun exampleTest() = runTest {
+ *     val elapsed = TimeSource.Monotonic.measureTime {
+ *         val deferred = async {
+ *             delay(1_000) // will be skipped
+ *             withContext(Dispatchers.Default) {
+ *                 delay(5_000) // Dispatchers.Default doesn't know about TestCoroutineScheduler
+ *             }
+ *         }
+ *         deferred.await()
+ *     }
+ *     println(elapsed) // about five seconds
+ * }
+ * ```
+ *
+ * ### Failures
+ *
+ * #### Test body failures
+ *
+ * If the created coroutine completes with an exception, then this exception will be thrown at the end of the test.
+ *
+ * #### Reported exceptions
+ *
+ * Unhandled exceptions will be thrown at the end of the test.
+ * If the uncaught exceptions happen after the test finishes, the error is propagated in a platform-specific manner.
+ * If the test coroutine completes with an exception, the unhandled exceptions are suppressed by it.
+ *
+ * #### Uncompleted coroutines
+ *
+ * This method requires that, after the test coroutine has completed, all the other coroutines launched inside
+ * [testBody] also complete, or are cancelled.
+ * Otherwise, the test will be failed (which, on JVM and Native, means that [runTest] itself will throw
+ * [AssertionError], whereas on JS, the `Promise` will fail with it).
+ *
+ * In the general case, if there are active jobs, it's impossible to detect if they are going to complete eventually due
+ * to the asynchronous nature of coroutines. In order to prevent tests hanging in this scenario, [runTest] will wait
+ * for [dispatchTimeoutMs] milliseconds (by default, 60 seconds) from the moment when [TestCoroutineScheduler] becomes
+ * idle before throwing [AssertionError]. If some dispatcher linked to [TestCoroutineScheduler] receives a
+ * task during that time, the timer gets reset.
+ *
+ * ### Configuration
+ *
+ * [context] can be used to affect the environment of the code under test. Beside just being passed to the coroutine
+ * scope created for the test, [context] also can be used to change how the test is executed.
+ * See the [TestScope] constructor function documentation for details.
+ *
+ * @throws IllegalArgumentException if the [context] is invalid. See the [TestScope] constructor docs for details.
+ */
+@ExperimentalCoroutinesApi
+public fun runTest(
+    context: CoroutineContext = EmptyCoroutineContext,
+    dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
+    testBody: suspend TestScope.() -> Unit
+): TestResult {
+    if (context[RunningInRunTest] != null)
+        throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.")
+    return TestScope(context + RunningInRunTest).runTest(dispatchTimeoutMs, testBody)
+}
+
+/**
+ * Performs [runTest] on an existing [TestScope].
+ */
+@ExperimentalCoroutinesApi
+public fun TestScope.runTest(
+    dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
+    testBody: suspend TestScope.() -> Unit
+): TestResult = asSpecificImplementation().let {
+    it.enter()
+    createTestResult {
+        runTestCoroutine(it, dispatchTimeoutMs, TestScopeImpl::tryGetCompletionCause, testBody) {
+            backgroundScope.cancel()
+            testScheduler.advanceUntilIdleOr { false }
+            it.leave()
+        }
+    }
+}
+
+/**
+ * Runs [testProcedure], creating a [TestResult].
+ */
+@Suppress("NO_ACTUAL_FOR_EXPECT") // actually suppresses `TestResult`
+internal expect fun createTestResult(testProcedure: suspend CoroutineScope.() -> Unit): TestResult
+
+/** A coroutine context element indicating that the coroutine is running inside `runTest`. */
+internal object RunningInRunTest : CoroutineContext.Key<RunningInRunTest>, CoroutineContext.Element {
+    override val key: CoroutineContext.Key<*>
+        get() = this
+
+    override fun toString(): String = "RunningInRunTest"
+}
+
+/** The default timeout to use when waiting for asynchronous completions of the coroutines managed by
+ * a [TestCoroutineScheduler]. */
+internal const val DEFAULT_DISPATCH_TIMEOUT_MS = 60_000L
+
+/**
+ * Run the [body][testBody] of the [test coroutine][coroutine], waiting for asynchronous completions for at most
+ * [dispatchTimeoutMs] milliseconds, and performing the [cleanup] procedure at the end.
+ *
+ * [tryGetCompletionCause] is the [JobSupport.completionCause], which is passed explicitly because it is protected.
+ *
+ * The [cleanup] procedure may either throw [UncompletedCoroutinesError] to denote that child coroutines were leaked, or
+ * return a list of uncaught exceptions that should be reported at the end of the test.
+ */
+internal suspend fun <T: AbstractCoroutine<Unit>> CoroutineScope.runTestCoroutine(
+    coroutine: T,
+    dispatchTimeoutMs: Long,
+    tryGetCompletionCause: T.() -> Throwable?,
+    testBody: suspend T.() -> Unit,
+    cleanup: () -> List<Throwable>,
+) {
+    val scheduler = coroutine.coroutineContext[TestCoroutineScheduler]!!
+    /** TODO: moving this [AbstractCoroutine.start] call outside [createTestResult] fails on JS. */
+    coroutine.start(CoroutineStart.UNDISPATCHED, coroutine) {
+        testBody()
+    }
+    /**
+     * The general procedure here is as follows:
+     * 1. Try running the work that the scheduler knows about, both background and foreground.
+     *
+     * 2. Wait until we run out of foreground work to do. This could mean one of the following:
+     *    * The main coroutine is already completed. This is checked separately; then we leave the procedure.
+     *    * It's switched to another dispatcher that doesn't know about the [TestCoroutineScheduler].
+     *    * Generally, it's waiting for something external (like a network request, or just an arbitrary callback).
+     *    * The test simply hanged.
+     *    * The main coroutine is waiting for some background work.
+     *
+     * 3. We await progress from things that are not the code under test:
+     *    the background work that the scheduler knows about, the external callbacks,
+     *    the work on dispatchers not linked to the scheduler, etc.
+     *
+     *    When we observe that the code under test can proceed, we go to step 1 again.
+     *    If there is no activity for [dispatchTimeoutMs] milliseconds, we consider the test to have hanged.
+     *
+     *    The background work is not running on a dedicated thread.
+     *    Instead, the test thread itself is used, by spawning a separate coroutine.
+     */
+    var completed = false
+    while (!completed) {
+        scheduler.advanceUntilIdle()
+        if (coroutine.isCompleted) {
+            /* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no
+               non-trivial dispatches. */
+            completed = true
+            continue
+        }
+        // in case progress depends on some background work, we need to keep spinning it.
+        val backgroundWorkRunner = launch(CoroutineName("background work runner")) {
+            while (true) {
+                scheduler.tryRunNextTaskUnless { !isActive }
+                // yield so that the `select` below has a chance to check if its conditions are fulfilled
+                yield()
+            }
+        }
+        try {
+            select<Unit> {
+                coroutine.onJoin {
+                    // observe that someone completed the test coroutine and leave without waiting for the timeout
+                    completed = true
+                }
+                scheduler.onDispatchEvent {
+                    // we received knowledge that `scheduler` observed a dispatch event, so we reset the timeout
+                }
+                onTimeout(dispatchTimeoutMs) {
+                    handleTimeout(coroutine, dispatchTimeoutMs, tryGetCompletionCause, cleanup)
+                }
+            }
+        } finally {
+            backgroundWorkRunner.cancelAndJoin()
+        }
+    }
+    coroutine.getCompletionExceptionOrNull()?.let { exception ->
+        val exceptions = try {
+            cleanup()
+        } catch (e: UncompletedCoroutinesError) {
+            // it's normal that some jobs are not completed if the test body has failed, won't clutter the output
+            emptyList()
+        }
+        (listOf(exception) + exceptions).throwAll()
+    }
+    cleanup().throwAll()
+}
+
+/**
+ * Invoked on timeout in [runTest]. Almost always just builds a nice [UncompletedCoroutinesError] and throws it.
+ * However, sometimes it detects that the coroutine completed, in which case it returns normally.
+ */
+private inline fun<T: AbstractCoroutine<Unit>> handleTimeout(
+    coroutine: T,
+    dispatchTimeoutMs: Long,
+    tryGetCompletionCause: T.() -> Throwable?,
+    cleanup: () -> List<Throwable>,
+) {
+    val uncaughtExceptions = try {
+        cleanup()
+    } catch (e: UncompletedCoroutinesError) {
+        // we expect these and will instead throw a more informative exception.
+        emptyList()
+    }
+    val activeChildren = coroutine.children.filter { it.isActive }.toList()
+    val completionCause = if (coroutine.isCancelled) coroutine.tryGetCompletionCause() else null
+    var message = "After waiting for $dispatchTimeoutMs ms"
+    if (completionCause == null)
+        message += ", the test coroutine is not completing"
+    if (activeChildren.isNotEmpty())
+        message += ", there were active child jobs: $activeChildren"
+    if (completionCause != null && activeChildren.isEmpty()) {
+        if (coroutine.isCompleted)
+            return
+        // TODO: can this really ever happen?
+        message += ", the test coroutine was not completed"
+    }
+    val error = UncompletedCoroutinesError(message)
+    completionCause?.let { cause -> error.addSuppressed(cause) }
+    uncaughtExceptions.forEach { error.addSuppressed(it) }
+    throw error
+}
+
+internal fun List<Throwable>.throwAll() {
+    firstOrNull()?.apply {
+        drop(1).forEach { addSuppressed(it) }
+        throw this
+    }
+}
diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt
new file mode 100644
index 0000000..e99fe8b
--- /dev/null
+++ b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2016-2021 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.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.test.internal.TestMainDispatcher
+import kotlin.coroutines.*
+
+/**
+ * Creates an instance of an unconfined [TestDispatcher].
+ *
+ * This dispatcher is similar to [Dispatchers.Unconfined]: the tasks that it executes are not confined to any particular
+ * thread and form an event loop; it's different in that it skips delays, as all [TestDispatcher]s do.
+ *
+ * Like [Dispatchers.Unconfined], this one does not provide guarantees about the execution order when several coroutines
+ * are queued in this dispatcher. However, we ensure that the [launch] and [async] blocks at the top level of [runTest]
+ * are entered eagerly. This allows launching child coroutines and not calling [runCurrent] for them to start executing.
+ *
+ * ```
+ * @Test
+ * fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
+ *   var entered = false
+ *   val deferred = CompletableDeferred<Unit>()
+ *   var completed = false
+ *   launch {
+ *     entered = true
+ *     deferred.await()
+ *     completed = true
+ *   }
+ *   assertTrue(entered) // `entered = true` already executed.
+ *   assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued.
+ *   deferred.complete(Unit) // resume the coroutine.
+ *   assertTrue(completed) // now the child coroutine is immediately completed.
+ * }
+ * ```
+ *
+ * Using this [TestDispatcher] can greatly simplify writing tests where it's not important which thread is used when and
+ * in which order the queued coroutines are executed.
+ * Another typical use case for this dispatcher is launching child coroutines that are resumed immediately, without
+ * going through a dispatch; this can be helpful for testing [Channel] and [StateFlow] usages.
+ *
+ * ```
+ * @Test
+ * fun testUnconfinedDispatcher() = runTest {
+ *   val values = mutableListOf<Int>()
+ *   val stateFlow = MutableStateFlow(0)
+ *   val job = launch(UnconfinedTestDispatcher(testScheduler)) {
+ *     stateFlow.collect {
+ *       values.add(it)
+ *     }
+ *   }
+ *   stateFlow.value = 1
+ *   stateFlow.value = 2
+ *   stateFlow.value = 3
+ *   job.cancel()
+ *   // each assignment will immediately resume the collecting child coroutine,
+ *   // so no values will be skipped.
+ *   assertEquals(listOf(0, 1, 2, 3), values)
+ * }
+ * ```
+ *
+ * Please be aware that, like [Dispatchers.Unconfined], this is a specific dispatcher with execution order
+ * guarantees that are unusual and not shared by most other dispatchers, so it can only be used reliably for testing
+ * functionality, not the specific order of actions.
+ * See [Dispatchers.Unconfined] for a discussion of the execution order guarantees.
+ *
+ * In order to support delay skipping, this dispatcher is linked to a [TestCoroutineScheduler], which is used to control
+ * the virtual time and can be shared among many test dispatchers.
+ * If no [scheduler] is passed as an argument, [Dispatchers.Main] is checked, and if it was mocked with a
+ * [TestDispatcher] via [Dispatchers.setMain], the [TestDispatcher.scheduler] of the mock dispatcher is used; if
+ * [Dispatchers.Main] is not mocked with a [TestDispatcher], a new [TestCoroutineScheduler] is created.
+ *
+ * Additionally, [name] can be set to distinguish each dispatcher instance when debugging.
+ *
+ * @see StandardTestDispatcher for a more predictable [TestDispatcher].
+ */
+@ExperimentalCoroutinesApi
+@Suppress("FunctionName")
+public fun UnconfinedTestDispatcher(
+    scheduler: TestCoroutineScheduler? = null,
+    name: String? = null
+): TestDispatcher = UnconfinedTestDispatcherImpl(
+    scheduler ?: TestMainDispatcher.currentTestScheduler ?: TestCoroutineScheduler(), name)
+
+private class UnconfinedTestDispatcherImpl(
+    override val scheduler: TestCoroutineScheduler,
+    private val name: String? = null
+) : TestDispatcher() {
+
+    override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
+
+    @Suppress("INVISIBLE_MEMBER")
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        checkSchedulerInContext(scheduler, context)
+        scheduler.sendDispatchEvent(context)
+
+        /** copy-pasted from [kotlinx.coroutines.Unconfined.dispatch] */
+        /** It can only be called by the [yield] function. See also code of [yield] function. */
+        val yieldContext = context[YieldContext]
+        if (yieldContext !== null) {
+            // report to "yield" that it is an unconfined dispatcher and don't call "block.run()"
+            yieldContext.dispatcherWasUnconfined = true
+            return
+        }
+        throw UnsupportedOperationException(
+            "Function UnconfinedTestCoroutineDispatcher.dispatch can only be used by " +
+                "the yield function. If you wrap Unconfined dispatcher in your code, make sure you properly delegate " +
+                "isDispatchNeeded and dispatch calls."
+        )
+    }
+
+    override fun toString(): String = "${name ?: "UnconfinedTestDispatcher"}[scheduler=$scheduler]"
+}
+
+/**
+ * Creates an instance of a [TestDispatcher] whose tasks are run inside calls to the [scheduler].
+ *
+ * This [TestDispatcher] instance does not itself execute any of the tasks. Instead, it always sends them to its
+ * [scheduler], which can then be accessed via [TestCoroutineScheduler.runCurrent],
+ * [TestCoroutineScheduler.advanceUntilIdle], or [TestCoroutineScheduler.advanceTimeBy], which will then execute these
+ * tasks in a blocking manner.
+ *
+ * In practice, this means that [launch] or [async] blocks will not be entered immediately (unless they are
+ * parameterized with [CoroutineStart.UNDISPATCHED]), and one should either call [TestCoroutineScheduler.runCurrent] to
+ * run these pending tasks, which will block until there are no more tasks scheduled at this point in time, or, when
+ * inside [runTest], call [yield] to yield the (only) thread used by [runTest] to the newly-launched coroutines.
+ *
+ * If no [scheduler] is passed as an argument, [Dispatchers.Main] is checked, and if it was mocked with a
+ * [TestDispatcher] via [Dispatchers.setMain], the [TestDispatcher.scheduler] of the mock dispatcher is used; if
+ * [Dispatchers.Main] is not mocked with a [TestDispatcher], a new [TestCoroutineScheduler] is created.
+ *
+ * One can additionally pass a [name] in order to more easily distinguish this dispatcher during debugging.
+ *
+ * @see UnconfinedTestDispatcher for a dispatcher that is not confined to any particular thread.
+ */
+@ExperimentalCoroutinesApi
+@Suppress("FunctionName")
+public fun StandardTestDispatcher(
+    scheduler: TestCoroutineScheduler? = null,
+    name: String? = null
+): TestDispatcher = StandardTestDispatcherImpl(
+    scheduler ?: TestMainDispatcher.currentTestScheduler ?: TestCoroutineScheduler(), name)
+
+private class StandardTestDispatcherImpl(
+    override val scheduler: TestCoroutineScheduler = TestCoroutineScheduler(),
+    private val name: String? = null
+) : TestDispatcher() {
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        scheduler.registerEvent(this, 0, block, context) { false }
+    }
+
+    override fun toString(): String = "${name ?: "StandardTestDispatcher"}[scheduler=$scheduler]"
+}
diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt
new file mode 100644
index 0000000..e735c6d
--- /dev/null
+++ b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2016-2021 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.channels.*
+import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
+import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.selects.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+import kotlin.time.*
+
+/**
+ * This is a scheduler for coroutines used in tests, providing the delay-skipping behavior.
+ *
+ * [Test dispatchers][TestDispatcher] are parameterized with a scheduler. Several dispatchers can share the
+ * same scheduler, in which case their knowledge about the virtual time will be synchronized. When the dispatchers
+ * require scheduling an event at a later point in time, they notify the scheduler, which will establish the order of
+ * the tasks.
+ *
+ * The scheduler can be queried to advance the time (via [advanceTimeBy]), run all the scheduled tasks advancing the
+ * virtual time as needed (via [advanceUntilIdle]), or run the tasks that are scheduled to run as soon as possible but
+ * haven't yet been dispatched (via [runCurrent]).
+ */
+@ExperimentalCoroutinesApi
+public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCoroutineScheduler),
+    CoroutineContext.Element {
+
+    /** @suppress */
+    public companion object Key : CoroutineContext.Key<TestCoroutineScheduler>
+
+    /** This heap stores the knowledge about which dispatchers are interested in which moments of virtual time. */
+    // TODO: all the synchronization is done via a separate lock, so a non-thread-safe priority queue can be used.
+    private val events = ThreadSafeHeap<TestDispatchEvent<Any>>()
+
+    /** Establishes that [currentTime] can't exceed the time of the earliest event in [events]. */
+    private val lock = SynchronizedObject()
+
+    /** This counter establishes some order on the events that happen at the same virtual time. */
+    private val count = atomic(0L)
+
+    /** The current virtual time in milliseconds. */
+    @ExperimentalCoroutinesApi
+    public var currentTime: Long = 0
+        get() = synchronized(lock) { field }
+        private set
+
+    /** A channel for notifying about the fact that a dispatch recently happened. */
+    private val dispatchEvents: Channel<Unit> = Channel(CONFLATED)
+
+    /**
+     * Registers a request for the scheduler to notify [dispatcher] at a virtual moment [timeDeltaMillis] milliseconds
+     * later via [TestDispatcher.processEvent], which will be called with the provided [marker] object.
+     *
+     * Returns the handler which can be used to cancel the registration.
+     */
+    internal fun <T : Any> registerEvent(
+        dispatcher: TestDispatcher,
+        timeDeltaMillis: Long,
+        marker: T,
+        context: CoroutineContext,
+        isCancelled: (T) -> Boolean
+    ): DisposableHandle {
+        require(timeDeltaMillis >= 0) { "Attempted scheduling an event earlier in time (with the time delta $timeDeltaMillis)" }
+        checkSchedulerInContext(this, context)
+        val count = count.getAndIncrement()
+        val isForeground = context[BackgroundWork] === null
+        return synchronized(lock) {
+            val time = addClamping(currentTime, timeDeltaMillis)
+            val event = TestDispatchEvent(dispatcher, count, time, marker as Any, isForeground) { isCancelled(marker) }
+            events.addLast(event)
+            /** can't be moved above: otherwise, [onDispatchEvent] could consume the token sent here before there's
+             * actually anything in the event queue. */
+            sendDispatchEvent(context)
+            DisposableHandle {
+                synchronized(lock) {
+                    events.remove(event)
+                }
+            }
+        }
+    }
+
+    /**
+     * Runs the next enqueued task, advancing the virtual time to the time of its scheduled awakening,
+     * unless [condition] holds.
+     */
+    internal fun tryRunNextTaskUnless(condition: () -> Boolean): Boolean {
+        val event = synchronized(lock) {
+            if (condition()) return false
+            val event = events.removeFirstOrNull() ?: return false
+            if (currentTime > event.time)
+                currentTimeAheadOfEvents()
+            currentTime = event.time
+            event
+        }
+        event.dispatcher.processEvent(event.time, event.marker)
+        return true
+    }
+
+    /**
+     * Runs the enqueued tasks in the specified order, advancing the virtual time as needed until there are no more
+     * tasks associated with the dispatchers linked to this scheduler.
+     *
+     * A breaking change from [TestCoroutineDispatcher.advanceTimeBy] is that it no longer returns the total number of
+     * milliseconds by which the execution of this method has advanced the virtual time. If you want to recreate that
+     * functionality, query [currentTime] before and after the execution to achieve the same result.
+     */
+    @ExperimentalCoroutinesApi
+    public fun advanceUntilIdle(): Unit = advanceUntilIdleOr { events.none(TestDispatchEvent<*>::isForeground) }
+
+    /**
+     * [condition]: guaranteed to be invoked under the lock.
+     */
+    internal fun advanceUntilIdleOr(condition: () -> Boolean) {
+        while (true) {
+            if (!tryRunNextTaskUnless(condition))
+                return
+        }
+    }
+
+    /**
+     * Runs the tasks that are scheduled to execute at this moment of virtual time.
+     */
+    @ExperimentalCoroutinesApi
+    public fun runCurrent() {
+        val timeMark = synchronized(lock) { currentTime }
+        while (true) {
+            val event = synchronized(lock) {
+                events.removeFirstIf { it.time <= timeMark } ?: return
+            }
+            event.dispatcher.processEvent(event.time, event.marker)
+        }
+    }
+
+    /**
+     * Moves the virtual clock of this dispatcher forward by [the specified amount][delayTimeMillis], running the
+     * scheduled tasks in the meantime.
+     *
+     * Breaking changes from [TestCoroutineDispatcher.advanceTimeBy]:
+     * * Intentionally doesn't return a `Long` value, as its use cases are unclear. We may restore it in the future;
+     *   please describe your use cases at [the issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues/).
+     *   For now, it's possible to query [currentTime] before and after execution of this method, to the same effect.
+     * * It doesn't run the tasks that are scheduled at exactly [currentTime] + [delayTimeMillis]. For example,
+     *   advancing the time by one millisecond used to run the tasks at the current millisecond *and* the next
+     *   millisecond, but now will stop just before executing any task starting at the next millisecond.
+     * * Overflowing the target time used to lead to nothing being done, but will now run the tasks scheduled at up to
+     *   (but not including) [Long.MAX_VALUE].
+     *
+     * @throws IllegalStateException if passed a negative [delay][delayTimeMillis].
+     */
+    @ExperimentalCoroutinesApi
+    public fun advanceTimeBy(delayTimeMillis: Long) {
+        require(delayTimeMillis >= 0) { "Can not advance time by a negative delay: $delayTimeMillis" }
+        val startingTime = currentTime
+        val targetTime = addClamping(startingTime, delayTimeMillis)
+        while (true) {
+            val event = synchronized(lock) {
+                val timeMark = currentTime
+                val event = events.removeFirstIf { targetTime > it.time }
+                when {
+                    event == null -> {
+                        currentTime = targetTime
+                        return
+                    }
+                    timeMark > event.time -> currentTimeAheadOfEvents()
+                    else -> {
+                        currentTime = event.time
+                        event
+                    }
+                }
+            }
+            event.dispatcher.processEvent(event.time, event.marker)
+        }
+    }
+
+    /**
+     * Checks that the only tasks remaining in the scheduler are cancelled.
+     */
+    internal fun isIdle(strict: Boolean = true): Boolean =
+        synchronized(lock) {
+            if (strict) events.isEmpty else events.none { !it.isCancelled() }
+        }
+
+    /**
+     * Notifies this scheduler about a dispatch event.
+     *
+     * [context] is the context in which the task will be dispatched.
+     */
+    internal fun sendDispatchEvent(context: CoroutineContext) {
+        if (context[BackgroundWork] !== BackgroundWork)
+            dispatchEvents.trySend(Unit)
+    }
+
+    /**
+     * Consumes the knowledge that a dispatch event happened recently.
+     */
+    internal val onDispatchEvent: SelectClause1<Unit> get() = dispatchEvents.onReceive
+
+    /**
+     * Returns the [TimeSource] representation of the virtual time of this scheduler.
+     */
+    @ExperimentalCoroutinesApi
+    @ExperimentalTime
+    public val timeSource: TimeSource = object : AbstractLongTimeSource(DurationUnit.MILLISECONDS) {
+        override fun read(): Long = currentTime
+    }
+}
+
+// Some error-throwing functions for pretty stack traces
+private fun currentTimeAheadOfEvents(): Nothing = invalidSchedulerState()
+
+private fun invalidSchedulerState(): Nothing =
+    throw IllegalStateException("The test scheduler entered an invalid state. Please report this at https://github.com/Kotlin/kotlinx.coroutines/issues.")
+
+/** [ThreadSafeHeap] node representing a scheduled task, ordered by the planned execution time. */
+private class TestDispatchEvent<T>(
+    @JvmField val dispatcher: TestDispatcher,
+    private val count: Long,
+    @JvmField val time: Long,
+    @JvmField val marker: T,
+    @JvmField val isForeground: Boolean,
+    // TODO: remove once the deprecated API is gone
+    @JvmField val isCancelled: () -> Boolean
+) : Comparable<TestDispatchEvent<*>>, ThreadSafeHeapNode {
+    override var heap: ThreadSafeHeap<*>? = null
+    override var index: Int = 0
+
+    override fun compareTo(other: TestDispatchEvent<*>) =
+        compareValuesBy(this, other, TestDispatchEvent<*>::time, TestDispatchEvent<*>::count)
+
+    override fun toString() = "TestDispatchEvent(time=$time, dispatcher=$dispatcher${if (isForeground) "" else ", background"})"
+}
+
+// works with positive `a`, `b`
+private fun addClamping(a: Long, b: Long): Long = (a + b).let { if (it >= 0) it else Long.MAX_VALUE }
+
+internal fun checkSchedulerInContext(scheduler: TestCoroutineScheduler, context: CoroutineContext) {
+    context[TestCoroutineScheduler]?.let {
+        check(it === scheduler) {
+            "Detected use of different schedulers. If you need to use several test coroutine dispatchers, " +
+                "create one `TestCoroutineScheduler` and pass it to each of them."
+        }
+    }
+}
+
+/**
+ * A coroutine context key denoting that the work is to be executed in the background.
+ * @see [TestScope.backgroundScope]
+ */
+internal object BackgroundWork : CoroutineContext.Key<BackgroundWork>, CoroutineContext.Element {
+    override val key: CoroutineContext.Key<*>
+        get() = this
+
+    override fun toString(): String = "BackgroundWork"
+}
+
+private fun<T> ThreadSafeHeap<T>.none(predicate: (T) -> Boolean) where T: ThreadSafeHeapNode, T: Comparable<T> =
+    find(predicate) == null
diff --git a/kotlinx-coroutines-test/common/src/TestDispatcher.kt b/kotlinx-coroutines-test/common/src/TestDispatcher.kt
new file mode 100644
index 0000000..348cc2f
--- /dev/null
+++ b/kotlinx-coroutines-test/common/src/TestDispatcher.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-2021 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.jvm.*
+
+/**
+ * A test dispatcher that can interface with a [TestCoroutineScheduler].
+ *
+ * The available implementations are:
+ * * [StandardTestDispatcher] is a dispatcher that places new tasks into a queue.
+ * * [UnconfinedTestDispatcher] is a dispatcher that behaves like [Dispatchers.Unconfined] while allowing to control
+ *   the virtual time.
+ */
+@ExperimentalCoroutinesApi
+public abstract class TestDispatcher internal constructor() : CoroutineDispatcher(), Delay {
+    /** The scheduler that this dispatcher is linked to. */
+    @ExperimentalCoroutinesApi
+    public abstract val scheduler: TestCoroutineScheduler
+
+    /** Notifies the dispatcher that it should process a single event marked with [marker] happening at time [time]. */
+    internal fun processEvent(time: Long, marker: Any) {
+        check(marker is Runnable)
+        marker.run()
+    }
+
+    /** @suppress */
+    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+        val timedRunnable = CancellableContinuationRunnable(continuation, this)
+        scheduler.registerEvent(this, timeMillis, timedRunnable, continuation.context, ::cancellableRunnableIsCancelled)
+    }
+
+    /** @suppress */
+    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
+        scheduler.registerEvent(this, timeMillis, block, context) { false }
+}
+
+/**
+ * This class exists to allow cleanup code to avoid throwing for cancelled continuations scheduled
+ * in the future.
+ */
+private class CancellableContinuationRunnable(
+    @JvmField val continuation: CancellableContinuation<Unit>,
+    private val dispatcher: CoroutineDispatcher
+) : Runnable {
+    override fun run() = with(dispatcher) { with(continuation) { resumeUndispatched(Unit) } }
+}
+
+private fun cancellableRunnableIsCancelled(runnable: CancellableContinuationRunnable): Boolean =
+    !runnable.continuation.isActive
diff --git a/kotlinx-coroutines-test/common/src/TestDispatchers.kt b/kotlinx-coroutines-test/common/src/TestDispatchers.kt
new file mode 100644
index 0000000..4454597
--- /dev/null
+++ b/kotlinx-coroutines-test/common/src/TestDispatchers.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:JvmName("TestDispatchers")
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.internal.*
+import kotlin.jvm.*
+
+/**
+ * Sets the given [dispatcher] as an underlying dispatcher of [Dispatchers.Main].
+ * All subsequent usages of [Dispatchers.Main] will use the given [dispatcher] under the hood.
+ *
+ * Using [TestDispatcher] as an argument has special behavior: subsequently-called [runTest], as well as
+ * [TestScope] and test dispatcher constructors, will use the [TestCoroutineScheduler] of the provided dispatcher.
+ *
+ * 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" }
+    getTestMainDispatcher().setDispatcher(dispatcher)
+}
+
+/**
+ * Resets state of the [Dispatchers.Main] to the original main dispatcher.
+ *
+ * For example, in Android, the Main thread dispatcher will be set as [Dispatchers.Main].
+ * This method undoes a dependency injection performed for tests, and so 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() {
+    getTestMainDispatcher().resetDispatcher()
+}
diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt
new file mode 100644
index 0000000..15d48a2
--- /dev/null
+++ b/kotlinx-coroutines-test/common/src/TestScope.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2016-2021 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.internal.*
+import kotlinx.coroutines.test.internal.*
+import kotlin.coroutines.*
+import kotlin.time.*
+
+/**
+ * A coroutine scope that for launching test coroutines.
+ *
+ * The scope provides the following functionality:
+ * * The [coroutineContext] includes a [coroutine dispatcher][TestDispatcher] that supports delay-skipping, using
+ *   a [TestCoroutineScheduler] for orchestrating the virtual time.
+ *   This scheduler is also available via the [testScheduler] property, and some helper extension
+ *   methods are defined to more conveniently interact with it: see [TestScope.currentTime], [TestScope.runCurrent],
+ *   [TestScope.advanceTimeBy], and [TestScope.advanceUntilIdle].
+ * * When inside [runTest], uncaught exceptions from the child coroutines of this scope will be reported at the end of
+ *   the test.
+ *   It is invalid for child coroutines to throw uncaught exceptions when outside the call to [TestScope.runTest]:
+ *   the only guarantee in this case is the best effort to deliver the exception.
+ *
+ * The usual way to access a [TestScope] is to call [runTest], but it can also be constructed manually, in order to
+ * use it to initialize the components that participate in the test.
+ *
+ * #### Differences from the deprecated [TestCoroutineScope]
+ *
+ * * This doesn't provide an equivalent of [TestCoroutineScope.cleanupTestCoroutines], and so can't be used as a
+ *   standalone mechanism for writing tests: it does require that [runTest] is eventually called.
+ *   The reason for this is that a proper cleanup procedure that supports using non-test dispatchers and arbitrary
+ *   coroutine suspensions would be equivalent to [runTest], but would also be more error-prone, due to the potential
+ *   for forgetting to perform the cleanup.
+ * * [TestCoroutineScope.advanceTimeBy] also calls [TestCoroutineScheduler.runCurrent] after advancing the virtual time.
+ * * No support for dispatcher pausing, like [DelayController] allows. [TestCoroutineDispatcher], which supported
+ *   pausing, is deprecated; now, instead of pausing a dispatcher, one can use [withContext] to run a dispatcher that's
+ *   paused by default, like [StandardTestDispatcher].
+ * * No access to the list of unhandled exceptions.
+ */
+@ExperimentalCoroutinesApi
+public sealed interface TestScope : CoroutineScope {
+    /**
+     * The delay-skipping scheduler used by the test dispatchers running the code in this scope.
+     */
+    @ExperimentalCoroutinesApi
+    public val testScheduler: TestCoroutineScheduler
+
+    /**
+     * A scope for background work.
+     *
+     * This scope is automatically cancelled when the test finishes.
+     * Additionally, while the coroutines in this scope are run as usual when
+     * using [advanceTimeBy] and [runCurrent], [advanceUntilIdle] will stop advancing the virtual time
+     * once only the coroutines in this scope are left unprocessed.
+     *
+     * Failures in coroutines in this scope do not terminate the test.
+     * Instead, they are reported at the end of the test.
+     * Likewise, failure in the [TestScope] itself will not affect its [backgroundScope],
+     * because there's no parent-child relationship between them.
+     *
+     * A typical use case for this scope is to launch tasks that would outlive the tested code in
+     * the production environment.
+     *
+     * In this example, the coroutine that continuously sends new elements to the channel will get
+     * cancelled:
+     * ```
+     * @Test
+     * fun testExampleBackgroundJob() = runTest {
+     *   val channel = Channel<Int>()
+     *   backgroundScope.launch {
+     *     var i = 0
+     *     while (true) {
+     *       channel.send(i++)
+     *     }
+     *   }
+     *   repeat(100) {
+     *     assertEquals(it, channel.receive())
+     *   }
+     * }
+     * ```
+     */
+    @ExperimentalCoroutinesApi
+    public val backgroundScope: CoroutineScope
+}
+
+/**
+ * The current virtual time on [testScheduler][TestScope.testScheduler].
+ * @see TestCoroutineScheduler.currentTime
+ */
+@ExperimentalCoroutinesApi
+public val TestScope.currentTime: Long
+    get() = testScheduler.currentTime
+
+/**
+ * Advances the [testScheduler][TestScope.testScheduler] to the point where there are no tasks remaining.
+ * @see TestCoroutineScheduler.advanceUntilIdle
+ */
+@ExperimentalCoroutinesApi
+public fun TestScope.advanceUntilIdle(): Unit = testScheduler.advanceUntilIdle()
+
+/**
+ * Run any tasks that are pending at the current virtual time, according to
+ * the [testScheduler][TestScope.testScheduler].
+ *
+ * @see TestCoroutineScheduler.runCurrent
+ */
+@ExperimentalCoroutinesApi
+public fun TestScope.runCurrent(): Unit = testScheduler.runCurrent()
+
+/**
+ * Moves the virtual clock of this dispatcher forward by [the specified amount][delayTimeMillis], running the
+ * scheduled tasks in the meantime.
+ *
+ * In contrast with `TestCoroutineScope.advanceTimeBy`, this function does not run the tasks scheduled at the moment
+ * [currentTime] + [delayTimeMillis].
+ *
+ * @throws IllegalStateException if passed a negative [delay][delayTimeMillis].
+ * @see TestCoroutineScheduler.advanceTimeBy
+ */
+@ExperimentalCoroutinesApi
+public fun TestScope.advanceTimeBy(delayTimeMillis: Long): Unit = testScheduler.advanceTimeBy(delayTimeMillis)
+
+/**
+ * The [test scheduler][TestScope.testScheduler] as a [TimeSource].
+ * @see TestCoroutineScheduler.timeSource
+ */
+@ExperimentalCoroutinesApi
+@ExperimentalTime
+public val TestScope.testTimeSource: TimeSource get() = testScheduler.timeSource
+
+/**
+ * Creates a [TestScope].
+ *
+ * It ensures that all the test module machinery is properly initialized.
+ * * If [context] doesn't provide a [TestCoroutineScheduler] for orchestrating the virtual time used for delay-skipping,
+ *   a new one is created, unless either
+ *   - a [TestDispatcher] is provided, in which case [TestDispatcher.scheduler] is used;
+ *   - at the moment of the creation of the scope, [Dispatchers.Main] is delegated to a [TestDispatcher], in which case
+ *     its [TestCoroutineScheduler] is used.
+ * * If [context] doesn't have a [TestDispatcher], a [StandardTestDispatcher] is created.
+ * * A [CoroutineExceptionHandler] is created that makes [TestCoroutineScope.cleanupTestCoroutines] throw if there were
+ *   any uncaught exceptions, or forwards the exceptions further in a platform-specific manner if the cleanup was
+ *   already performed when an exception happened. Passing a [CoroutineExceptionHandler] is illegal, unless it's an
+ *   [UncaughtExceptionCaptor], in which case the behavior is preserved for the time being for backward compatibility.
+ *   If you need to have a specific [CoroutineExceptionHandler], please pass it to [launch] on an already-created
+ *   [TestCoroutineScope] and share your use case at
+ *   [our issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues).
+ * * If [context] provides a [Job], that job is used as a parent for the new scope.
+ *
+ * @throws IllegalArgumentException if [context] has both [TestCoroutineScheduler] and a [TestDispatcher] linked to a
+ * different scheduler.
+ * @throws IllegalArgumentException if [context] has a [ContinuationInterceptor] that is not a [TestDispatcher].
+ * @throws IllegalArgumentException if [context] has an [CoroutineExceptionHandler] that is not an
+ * [UncaughtExceptionCaptor].
+ */
+@ExperimentalCoroutinesApi
+@Suppress("FunctionName")
+public fun TestScope(context: CoroutineContext = EmptyCoroutineContext): TestScope {
+    val ctxWithDispatcher = context.withDelaySkipping()
+    var scope: TestScopeImpl? = null
+    val exceptionHandler = when (ctxWithDispatcher[CoroutineExceptionHandler]) {
+        null -> CoroutineExceptionHandler { _, exception ->
+            scope!!.reportException(exception)
+        }
+        else -> throw IllegalArgumentException(
+            "A CoroutineExceptionHandler was passed to TestScope. " +
+                "Please pass it as an argument to a `launch` or `async` block on an already-created scope " +
+                "if uncaught exceptions require special treatment."
+        )
+    }
+    return TestScopeImpl(ctxWithDispatcher + exceptionHandler).also { scope = it }
+}
+
+/**
+ * Adds a [TestDispatcher] and a [TestCoroutineScheduler] to the context if there aren't any already.
+ *
+ * @throws IllegalArgumentException if both a [TestCoroutineScheduler] and a [TestDispatcher] are passed.
+ * @throws IllegalArgumentException if a [ContinuationInterceptor] is passed that is not a [TestDispatcher].
+ */
+internal fun CoroutineContext.withDelaySkipping(): CoroutineContext {
+    val dispatcher: TestDispatcher = when (val dispatcher = get(ContinuationInterceptor)) {
+        is TestDispatcher -> {
+            val ctxScheduler = get(TestCoroutineScheduler)
+            if (ctxScheduler != null) {
+                require(dispatcher.scheduler === ctxScheduler) {
+                    "Both a TestCoroutineScheduler $ctxScheduler and TestDispatcher $dispatcher linked to " +
+                        "another scheduler were passed."
+                }
+            }
+            dispatcher
+        }
+        null -> StandardTestDispatcher(get(TestCoroutineScheduler))
+        else -> throw IllegalArgumentException("Dispatcher must implement TestDispatcher: $dispatcher")
+    }
+    return this + dispatcher + dispatcher.scheduler
+}
+
+internal class TestScopeImpl(context: CoroutineContext) :
+    AbstractCoroutine<Unit>(context, initParentJob = true, active = true), TestScope {
+
+    override val testScheduler get() = context[TestCoroutineScheduler]!!
+
+    private var entered = false
+    private var finished = false
+    private val uncaughtExceptions = mutableListOf<Throwable>()
+    private val lock = SynchronizedObject()
+
+    override val backgroundScope: CoroutineScope =
+        CoroutineScope(coroutineContext + BackgroundWork + ReportingSupervisorJob {
+            if (it !is CancellationException) reportException(it)
+        })
+
+    /** Called upon entry to [runTest]. Will throw if called more than once. */
+    fun enter() {
+        val exceptions = synchronized(lock) {
+            if (entered)
+                throw IllegalStateException("Only a single call to `runTest` can be performed during one test.")
+            entered = true
+            check(!finished)
+            uncaughtExceptions
+        }
+        if (exceptions.isNotEmpty()) {
+            throw UncaughtExceptionsBeforeTest().apply {
+                for (e in exceptions)
+                    addSuppressed(e)
+            }
+        }
+    }
+
+    /** Called at the end of the test. May only be called once. */
+    fun leave(): List<Throwable> {
+        val exceptions = synchronized(lock) {
+            check(entered && !finished)
+            finished = true
+            uncaughtExceptions
+        }
+        val activeJobs = children.filter { it.isActive }.toList() // only non-empty if used with `runBlockingTest`
+        if (exceptions.isEmpty()) {
+            if (activeJobs.isNotEmpty())
+                throw UncompletedCoroutinesError(
+                    "Active jobs found during the tear-down. " +
+                        "Ensure that all coroutines are completed or cancelled by your test. " +
+                        "The active jobs: $activeJobs"
+                )
+            if (!testScheduler.isIdle())
+                throw UncompletedCoroutinesError(
+                    "Unfinished coroutines found during the tear-down. " +
+                        "Ensure that all coroutines are completed or cancelled by your test."
+                )
+        }
+        return exceptions
+    }
+
+    /** Stores an exception to report after [runTest], or rethrows it if not inside [runTest]. */
+    fun reportException(throwable: Throwable) {
+        synchronized(lock) {
+            if (finished) {
+                throw throwable
+            } else {
+                @Suppress("INVISIBLE_MEMBER")
+                for (existingThrowable in uncaughtExceptions) {
+                    // avoid reporting exceptions that already were reported.
+                    if (unwrap(throwable) == unwrap(existingThrowable))
+                        return
+                }
+                uncaughtExceptions.add(throwable)
+                if (!entered)
+                    throw UncaughtExceptionsBeforeTest().apply { addSuppressed(throwable) }
+            }
+        }
+    }
+
+    /** Throws an exception if the coroutine is not completing. */
+    fun tryGetCompletionCause(): Throwable? = completionCause
+
+    override fun toString(): String =
+        "TestScope[" + (if (finished) "test ended" else if (entered) "test started" else "test not started") + "]"
+}
+
+/** Use the knowledge that any [TestScope] that we receive is necessarily a [TestScopeImpl]. */
+@Suppress("NO_ELSE_IN_WHEN") // TODO: a problem with `sealed` in MPP not allowing total pattern-matching
+internal fun TestScope.asSpecificImplementation(): TestScopeImpl = when (this) {
+    is TestScopeImpl -> this
+}
+
+internal class UncaughtExceptionsBeforeTest : IllegalStateException(
+    "There were uncaught exceptions in coroutines launched from TestScope before the test started. Please avoid this," +
+        " as such exceptions are also reported in a platform-dependent manner so that they are not lost."
+)
+
+/**
+ * Thrown when a test has completed and there are tasks that are not completed or cancelled.
+ */
+@ExperimentalCoroutinesApi
+internal class UncompletedCoroutinesError(message: String) : AssertionError(message)
diff --git a/kotlinx-coroutines-test/common/src/internal/ReportingSupervisorJob.kt b/kotlinx-coroutines-test/common/src/internal/ReportingSupervisorJob.kt
new file mode 100644
index 0000000..e3091bc
--- /dev/null
+++ b/kotlinx-coroutines-test/common/src/internal/ReportingSupervisorJob.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-2022 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.*
+
+/**
+ * A variant of [SupervisorJob] that additionally notifies about child failures via a callback.
+ */
+@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER")
+internal class ReportingSupervisorJob(parent: Job? = null, val onChildCancellation: (Throwable) -> Unit) :
+    JobImpl(parent) {
+    override fun childCancelled(cause: Throwable): Boolean =
+        try {
+            onChildCancellation(cause)
+        } catch (e: Throwable) {
+            cause.addSuppressed(e)
+            /* the coroutine context does not matter here, because we're only interested in reporting this exception
+            to the platform-specific global handler, not to a [CoroutineExceptionHandler] of any sort. */
+            handleCoroutineException(this, cause)
+        }.let { false }
+}
diff --git a/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt
new file mode 100644
index 0000000..24e093b
--- /dev/null
+++ b/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test.internal
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.*
+import kotlin.coroutines.*
+
+/**
+ * The testable main dispatcher used by kotlinx-coroutines-test.
+ * It is a [MainCoroutineDispatcher] that delegates all actions to a settable delegate.
+ */
+internal class TestMainDispatcher(delegate: CoroutineDispatcher):
+    MainCoroutineDispatcher(),
+    Delay
+{
+    private val mainDispatcher = delegate
+    private var delegate = NonConcurrentlyModifiable(mainDispatcher, "Dispatchers.Main")
+
+    private val delay
+        get() = delegate.value as? Delay ?: defaultDelay
+
+    override val immediate: MainCoroutineDispatcher
+        get() = (delegate.value as? MainCoroutineDispatcher)?.immediate ?: this
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) = delegate.value.dispatch(context, block)
+
+    override fun isDispatchNeeded(context: CoroutineContext): Boolean = delegate.value.isDispatchNeeded(context)
+
+    override fun dispatchYield(context: CoroutineContext, block: Runnable) = delegate.value.dispatchYield(context, block)
+
+    fun setDispatcher(dispatcher: CoroutineDispatcher) {
+        delegate.value = dispatcher
+    }
+
+    fun resetDispatcher() {
+        delegate.value = mainDispatcher
+    }
+
+    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) =
+        delay.scheduleResumeAfterDelay(timeMillis, continuation)
+
+    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
+        delay.invokeOnTimeout(timeMillis, block, context)
+
+    companion object {
+        internal val currentTestDispatcher
+            get() = (Dispatchers.Main as? TestMainDispatcher)?.delegate?.value as? TestDispatcher
+
+        internal val currentTestScheduler
+            get() = currentTestDispatcher?.scheduler
+    }
+
+    /**
+     * A wrapper around a value that attempts to throw when writing happens concurrently with reading.
+     *
+     * The read operations never throw. Instead, the failures detected inside them will be remembered and thrown on the
+     * next modification.
+     */
+    private class NonConcurrentlyModifiable<T>(initialValue: T, private val name: String) {
+        private val readers = atomic(0) // number of concurrent readers
+        private val isWriting = atomic(false) // a modification is happening currently
+        private val exceptionWhenReading: AtomicRef<Throwable?> = atomic(null) // exception from reading
+        private val _value = atomic(initialValue) // the backing field for the value
+
+        private fun concurrentWW() = IllegalStateException("$name is modified concurrently")
+        private fun concurrentRW() = IllegalStateException("$name is used concurrently with setting it")
+
+        var value: T
+            get() {
+                readers.incrementAndGet()
+                if (isWriting.value) exceptionWhenReading.value = concurrentRW()
+                val result = _value.value
+                readers.decrementAndGet()
+                return result
+            }
+            set(value) {
+                exceptionWhenReading.getAndSet(null)?.let { throw it }
+                if (readers.value != 0) throw concurrentRW()
+                if (!isWriting.compareAndSet(expect = false, update = true)) throw concurrentWW()
+                _value.value = value
+                isWriting.value = false
+                if (readers.value != 0) throw concurrentRW()
+            }
+    }
+}
+
+@Suppress("INVISIBLE_MEMBER")
+private val defaultDelay
+    inline get() = DefaultDelay
+
+@Suppress("INVISIBLE_MEMBER")
+internal expect fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher
diff --git a/kotlinx-coroutines-test/common/test/Helpers.kt b/kotlinx-coroutines-test/common/test/Helpers.kt
new file mode 100644
index 0000000..98375b0
--- /dev/null
+++ b/kotlinx-coroutines-test/common/test/Helpers.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016-2021 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 kotlin.test.*
+import kotlin.time.*
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * The number of milliseconds that is sure not to pass [assertRunsFast].
+ */
+const val SLOW = 100_000L
+
+/**
+ * Asserts that a block completed within [timeout].
+ */
+@OptIn(ExperimentalTime::class)
+inline fun <T> assertRunsFast(timeout: Duration, block: () -> T): T {
+    val result: T
+    val elapsed = TimeSource.Monotonic.measureTime { result = block() }
+    assertTrue("Should complete in $timeout, but took $elapsed") { elapsed < timeout }
+    return result
+}
+
+/**
+ * Asserts that a block completed within two seconds.
+ */
+inline fun <T> assertRunsFast(block: () -> T): T = assertRunsFast(2.seconds, block)
+
+/**
+ * Passes [test] as an argument to [block], but as a function returning not a [TestResult] but [Unit].
+*/
+expect fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult
+
+class TestException(message: String? = null): Exception(message)
+
+/**
+ * A class inheriting from which allows to check the execution order inside tests.
+ *
+ * @see TestBase
+ */
+open class OrderedExecutionTestBase {
+    private val actionIndex = atomic(0)
+    private val finished = atomic(false)
+
+    /** Expect the next action to be [index] in order. */
+    protected fun expect(index: Int) {
+        val wasIndex = actionIndex.incrementAndGet()
+        check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
+    }
+
+    /** Expect this action to be final, with the given [index]. */
+    protected fun finish(index: Int) {
+        expect(index)
+        check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" }
+    }
+
+    @AfterTest
+    fun ensureFinishCalls() {
+        assertTrue(finished.value || actionIndex.value == 0, "Expected `finish` to be called")
+    }
+}
+
+internal fun <T> T.void() { }
+
+@OptionalExpectation
+expect annotation class NoJs()
+
+@OptionalExpectation
+expect annotation class NoNative()
diff --git a/kotlinx-coroutines-test/common/test/RunTestTest.kt b/kotlinx-coroutines-test/common/test/RunTestTest.kt
new file mode 100644
index 0000000..1430d83
--- /dev/null
+++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2016-2021 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.internal.*
+import kotlinx.coroutines.flow.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class RunTestTest {
+
+    /** Tests that [withContext] that sends work to other threads works in [runTest]. */
+    @Test
+    fun testWithContextDispatching() = runTest {
+        var counter = 0
+        withContext(Dispatchers.Default) {
+            counter += 1
+        }
+        assertEquals(counter, 1)
+    }
+
+    /** Tests that joining [GlobalScope.launch] works in [runTest]. */
+    @Test
+    fun testJoiningForkedJob() = runTest {
+        var counter = 0
+        val job = GlobalScope.launch {
+            counter += 1
+        }
+        job.join()
+        assertEquals(counter, 1)
+    }
+
+    /** Tests [suspendCoroutine] not failing [runTest]. */
+    @Test
+    fun testSuspendCoroutine() = runTest {
+        val answer = suspendCoroutine<Int> {
+            it.resume(42)
+        }
+        assertEquals(42, answer)
+    }
+
+    /** Tests that [runTest] attempts to detect it being run inside another [runTest] and failing in such scenarios. */
+    @Test
+    fun testNestedRunTestForbidden() = runTest {
+        assertFailsWith<IllegalStateException> {
+            runTest { }
+        }
+    }
+
+    /** Tests that even the dispatch timeout of `0` is fine if all the dispatches go through the same scheduler. */
+    @Test
+    fun testRunTestWithZeroTimeoutWithControlledDispatches() = runTest(dispatchTimeoutMs = 0) {
+        // below is some arbitrary concurrent code where all dispatches go through the same scheduler.
+        launch {
+            delay(2000)
+        }
+        val deferred = async {
+            val job = launch(StandardTestDispatcher(testScheduler)) {
+                launch {
+                    delay(500)
+                }
+                delay(1000)
+            }
+            job.join()
+        }
+        deferred.await()
+    }
+
+    /** Tests that a dispatch timeout of `0` will fail the test if there are some dispatches outside the scheduler. */
+    @Test
+    @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native
+    fun testRunTestWithZeroTimeoutWithUncontrolledDispatches() = testResultMap({ fn ->
+        assertFailsWith<UncompletedCoroutinesError> { fn() }
+    }) {
+        runTest(dispatchTimeoutMs = 0) {
+            withContext(Dispatchers.Default) {
+                delay(10)
+                3
+            }
+            fail("shouldn't be reached")
+        }
+    }
+
+    /** Tests that too low of a dispatch timeout causes crashes. */
+    @Test
+    @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native
+    fun testRunTestWithSmallTimeout() = testResultMap({ fn ->
+        assertFailsWith<UncompletedCoroutinesError> { fn() }
+    }) {
+        runTest(dispatchTimeoutMs = 100) {
+            withContext(Dispatchers.Default) {
+                delay(10000)
+                3
+            }
+            fail("shouldn't be reached")
+        }
+    }
+
+    /** Tests that, on timeout, the names of the active coroutines are listed,
+     * whereas the names of the completed ones are not. */
+    @Test
+    @NoJs
+    @NoNative
+    fun testListingActiveCoroutinesOnTimeout(): TestResult {
+        val name1 = "GoodUniqueName"
+        val name2 = "BadUniqueName"
+        return testResultMap({
+            try {
+                it()
+                fail("unreached")
+            } catch (e: UncompletedCoroutinesError) {
+                assertTrue((e.message ?: "").contains(name1))
+                assertFalse((e.message ?: "").contains(name2))
+            }
+        }) {
+            runTest(dispatchTimeoutMs = 10) {
+                launch(CoroutineName(name1)) {
+                    CompletableDeferred<Unit>().await()
+                }
+                launch(CoroutineName(name2)) {
+                }
+            }
+        }
+    }
+
+    /** Tests that the [UncompletedCoroutinesError] suppresses an exception with which the coroutine is completing. */
+    @Test
+    fun testFailureWithPendingCoroutine() = testResultMap({
+        try {
+            it()
+            fail("unreached")
+        } catch (e: UncompletedCoroutinesError) {
+            @Suppress("INVISIBLE_MEMBER")
+            val suppressed = unwrap(e).suppressedExceptions
+            assertEquals(1, suppressed.size)
+            assertIs<TestException>(suppressed[0]).also {
+                assertEquals("A", it.message)
+            }
+        }
+    }) {
+        runTest(dispatchTimeoutMs = 10) {
+            launch {
+                withContext(NonCancellable) {
+                    awaitCancellation()
+                }
+            }
+            yield()
+            throw TestException("A")
+        }
+    }
+
+    /** Tests that real delays can be accounted for with a large enough dispatch timeout. */
+    @Test
+    fun testRunTestWithLargeTimeout() = runTest(dispatchTimeoutMs = 5000) {
+        withContext(Dispatchers.Default) {
+            delay(50)
+        }
+    }
+
+    /** Tests uncaught exceptions being suppressed by the dispatch timeout error. */
+    @Test
+    @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native
+    fun testRunTestTimingOutAndThrowing() = testResultMap({ fn ->
+        try {
+            fn()
+            fail("unreached")
+        } catch (e: UncompletedCoroutinesError) {
+            @Suppress("INVISIBLE_MEMBER")
+            val suppressed = unwrap(e).suppressedExceptions
+            assertEquals(1, suppressed.size)
+            assertIs<TestException>(suppressed[0]).also {
+                assertEquals("A", it.message)
+            }
+        }
+    }) {
+        runTest(dispatchTimeoutMs = 1) {
+            coroutineContext[CoroutineExceptionHandler]!!.handleException(coroutineContext, TestException("A"))
+            withContext(Dispatchers.Default) {
+                delay(10000)
+                3
+            }
+            fail("shouldn't be reached")
+        }
+    }
+
+    /** Tests that passing invalid contexts to [runTest] causes it to fail (on JS, without forking). */
+    @Test
+    fun testRunTestWithIllegalContext() {
+        for (ctx in TestScopeTest.invalidContexts) {
+            assertFailsWith<IllegalArgumentException> {
+                runTest(ctx) { }
+            }
+        }
+    }
+
+    /** Tests that throwing exceptions in [runTest] fails the test with them. */
+    @Test
+    fun testThrowingInRunTestBody() = testResultMap({
+        assertFailsWith<RuntimeException> { it() }
+    }) {
+        runTest {
+            throw RuntimeException()
+        }
+    }
+
+    /** Tests that throwing exceptions in pending tasks [runTest] fails the test with them. */
+    @Test
+    fun testThrowingInRunTestPendingTask() = testResultMap({
+        assertFailsWith<RuntimeException> { it() }
+    }) {
+        runTest {
+            launch {
+                delay(SLOW)
+                throw RuntimeException()
+            }
+        }
+    }
+
+    @Test
+    fun reproducer2405() = runTest {
+        val dispatcher = StandardTestDispatcher(testScheduler)
+        var collectedError = false
+        withContext(dispatcher) {
+            flow { emit(1) }
+                .combine(
+                    flow<String> { throw IllegalArgumentException() }
+                ) { int, string -> int.toString() + string }
+                .catch { emit("error") }
+                .collect {
+                    assertEquals("error", it)
+                    collectedError = true
+                }
+        }
+        assertTrue(collectedError)
+    }
+
+    /** Tests that, once the test body has thrown, the child coroutines are cancelled. */
+    @Test
+    fun testChildrenCancellationOnTestBodyFailure(): TestResult {
+        var job: Job? = null
+        return testResultMap({
+            assertFailsWith<AssertionError> { it() }
+            assertTrue(job!!.isCancelled)
+        }) {
+            runTest {
+                job = launch {
+                    while (true) {
+                        delay(1000)
+                    }
+                }
+                throw AssertionError()
+            }
+        }
+    }
+
+    /** Tests that [runTest] reports [TimeoutCancellationException]. */
+    @Test
+    fun testTimeout() = testResultMap({
+        assertFailsWith<TimeoutCancellationException> { it() }
+    }) {
+        runTest {
+            withTimeout(50) {
+                launch {
+                    delay(1000)
+                }
+            }
+        }
+    }
+
+    /** Checks that [runTest] throws the root cause and not [JobCancellationException] when a child coroutine throws. */
+    @Test
+    fun testRunTestThrowsRootCause() = testResultMap({
+        assertFailsWith<TestException> { it() }
+    }) {
+        runTest {
+            launch {
+                throw TestException()
+            }
+        }
+    }
+
+    /** Tests that [runTest] completes its job. */
+    @Test
+    fun testCompletesOwnJob(): TestResult {
+        var handlerCalled = false
+        return testResultMap({
+            it()
+            assertTrue(handlerCalled)
+        }) {
+            runTest {
+                coroutineContext.job.invokeOnCompletion {
+                    handlerCalled = true
+                }
+            }
+        }
+    }
+
+    /** Tests that [runTest] doesn't complete the job that was passed to it as an argument. */
+    @Test
+    fun testDoesNotCompleteGivenJob(): TestResult {
+        var handlerCalled = false
+        val job = Job()
+        job.invokeOnCompletion {
+            handlerCalled = true
+        }
+        return testResultMap({
+            it()
+            assertFalse(handlerCalled)
+            assertEquals(0, job.children.filter { it.isActive }.count())
+        }) {
+            runTest(job) {
+                assertTrue(coroutineContext.job in job.children)
+            }
+        }
+    }
+
+    /** Tests that, when the test body fails, the reported exceptions are suppressed. */
+    @Test
+    fun testSuppressedExceptions() = testResultMap({
+        try {
+            it()
+            fail("should not be reached")
+        } catch (e: TestException) {
+            assertEquals("w", e.message)
+            val suppressed = e.suppressedExceptions +
+                (e.suppressedExceptions.firstOrNull()?.suppressedExceptions ?: emptyList())
+            assertEquals(3, suppressed.size)
+            assertEquals("x", suppressed[0].message)
+            assertEquals("y", suppressed[1].message)
+            assertEquals("z", suppressed[2].message)
+        }
+    }) {
+        runTest {
+            launch(SupervisorJob()) { throw TestException("x") }
+            launch(SupervisorJob()) { throw TestException("y") }
+            launch(SupervisorJob()) { throw TestException("z") }
+            throw TestException("w")
+        }
+    }
+
+    /** Tests that [TestCoroutineScope.runTest] does not inherit the exception handler and works. */
+    @Test
+    fun testScopeRunTestExceptionHandler(): TestResult {
+        val scope = TestScope()
+        return testResultMap({
+            try {
+                it()
+                fail("should not be reached")
+            } catch (e: TestException) {
+                // expected
+            }
+        }) {
+            scope.runTest {
+                launch(SupervisorJob()) { throw TestException("x") }
+            }
+        }
+    }
+
+    /**
+     * Tests that if the main coroutine is completed without a dispatch, [runTest] will not consider this to be
+     * inactivity.
+     *
+     * The test will hang if this is not the case.
+     */
+    @Test
+    fun testCoroutineCompletingWithoutDispatch() = runTest(dispatchTimeoutMs = Long.MAX_VALUE) {
+        launch(Dispatchers.Default) { delay(100) }
+    }
+}
diff --git a/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt b/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt
new file mode 100644
index 0000000..d66be9b
--- /dev/null
+++ b/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016-2021 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.flow.*
+import kotlin.test.*
+
+class StandardTestDispatcherTest: OrderedExecutionTestBase() {
+
+    private val scope = TestScope(StandardTestDispatcher())
+
+    @BeforeTest
+    fun init() {
+        scope.asSpecificImplementation().enter()
+    }
+
+    @AfterTest
+    fun cleanup() {
+        scope.runCurrent()
+        assertEquals(listOf(), scope.asSpecificImplementation().leave())
+    }
+
+    /** Tests that the [StandardTestDispatcher] follows an execution order similar to `runBlocking`. */
+    @Test
+    fun testFlowsNotSkippingValues() = scope.launch {
+        // https://github.com/Kotlin/kotlinx.coroutines/issues/1626#issuecomment-554632852
+        val list = flowOf(1).onStart { emit(0) }
+            .combine(flowOf("A")) { int, str -> "$str$int" }
+            .toList()
+        assertEquals(list, listOf("A0", "A1"))
+    }.void()
+
+    /** Tests that each [launch] gets dispatched. */
+    @Test
+    fun testLaunchDispatched() = scope.launch {
+        expect(1)
+        launch {
+            expect(3)
+        }
+        finish(2)
+    }.void()
+
+    /** Tests that dispatching is done in a predictable order and [yield] puts this task at the end of the queue. */
+    @Test
+    fun testYield() = scope.launch {
+        expect(1)
+        scope.launch {
+            expect(3)
+            yield()
+            expect(6)
+        }
+        scope.launch {
+            expect(4)
+            yield()
+            finish(7)
+        }
+        expect(2)
+        yield()
+        expect(5)
+    }.void()
+
+    /** Tests that the [TestCoroutineScheduler] used for [Dispatchers.Main] gets used by default. */
+    @Test
+    @NoNative
+    fun testSchedulerReuse() {
+        val dispatcher1 = StandardTestDispatcher()
+        Dispatchers.setMain(dispatcher1)
+        try {
+            val dispatcher2 = StandardTestDispatcher()
+            assertSame(dispatcher1.scheduler, dispatcher2.scheduler)
+        } finally {
+            Dispatchers.resetMain()
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt
new file mode 100644
index 0000000..d050e9c
--- /dev/null
+++ b/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2016-2021 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.test.*
+import kotlin.time.*
+import kotlin.time.Duration.Companion.seconds
+
+class TestCoroutineSchedulerTest {
+    /** Tests that `TestCoroutineScheduler` attempts to detect if there are several instances of it. */
+    @Test
+    fun testContextElement() = runTest {
+        assertFailsWith<IllegalStateException> {
+            withContext(StandardTestDispatcher()) {
+            }
+        }
+    }
+
+    /** Tests that, as opposed to [DelayController.advanceTimeBy] or [TestCoroutineScope.advanceTimeBy],
+     * [TestCoroutineScheduler.advanceTimeBy] doesn't run the tasks scheduled at the target moment. */
+    @Test
+    fun testAdvanceTimeByDoesNotRunCurrent() = runTest {
+        var entered = false
+        launch {
+            delay(15)
+            entered = true
+        }
+        testScheduler.advanceTimeBy(15)
+        assertFalse(entered)
+        testScheduler.runCurrent()
+        assertTrue(entered)
+    }
+
+    /** Tests that [TestCoroutineScheduler.advanceTimeBy] doesn't accept negative delays. */
+    @Test
+    fun testAdvanceTimeByWithNegativeDelay() {
+        val scheduler = TestCoroutineScheduler()
+        assertFailsWith<IllegalArgumentException> {
+            scheduler.advanceTimeBy(-1)
+        }
+    }
+
+    /** Tests that if [TestCoroutineScheduler.advanceTimeBy] encounters an arithmetic overflow, all the tasks scheduled
+     * until the moment [Long.MAX_VALUE] get run. */
+    @Test
+    fun testAdvanceTimeByEnormousDelays() = forTestDispatchers {
+        assertRunsFast {
+            with (TestScope(it)) {
+                launch {
+                    val initialDelay = 10L
+                    delay(initialDelay)
+                    assertEquals(initialDelay, currentTime)
+                    var enteredInfinity = false
+                    launch {
+                        delay(Long.MAX_VALUE - 1) // delay(Long.MAX_VALUE) does nothing
+                        assertEquals(Long.MAX_VALUE, currentTime)
+                        enteredInfinity = true
+                    }
+                    var enteredNearInfinity = false
+                    launch {
+                        delay(Long.MAX_VALUE - initialDelay - 1)
+                        assertEquals(Long.MAX_VALUE - 1, currentTime)
+                        enteredNearInfinity = true
+                    }
+                    testScheduler.advanceTimeBy(Long.MAX_VALUE)
+                    assertFalse(enteredInfinity)
+                    assertTrue(enteredNearInfinity)
+                    assertEquals(Long.MAX_VALUE, currentTime)
+                    testScheduler.runCurrent()
+                    assertTrue(enteredInfinity)
+                }
+                testScheduler.advanceUntilIdle()
+            }
+        }
+    }
+
+    /** Tests the basic functionality of [TestCoroutineScheduler.advanceTimeBy]. */
+    @Test
+    fun testAdvanceTimeBy() = runTest {
+        assertRunsFast {
+            var stage = 1
+            launch {
+                delay(1_000)
+                assertEquals(1_000, currentTime)
+                stage = 2
+                delay(500)
+                assertEquals(1_500, currentTime)
+                stage = 3
+                delay(501)
+                assertEquals(2_001, currentTime)
+                stage = 4
+            }
+            assertEquals(1, stage)
+            assertEquals(0, currentTime)
+            advanceTimeBy(2_000)
+            assertEquals(3, stage)
+            assertEquals(2_000, currentTime)
+            advanceTimeBy(2)
+            assertEquals(4, stage)
+            assertEquals(2_002, currentTime)
+        }
+    }
+
+    /** Tests the basic functionality of [TestCoroutineScheduler.runCurrent]. */
+    @Test
+    fun testRunCurrent() = runTest {
+        var stage = 0
+        launch {
+            delay(1)
+            ++stage
+            delay(1)
+            stage += 10
+        }
+        launch {
+            delay(1)
+            ++stage
+            delay(1)
+            stage += 10
+        }
+        testScheduler.advanceTimeBy(1)
+        assertEquals(0, stage)
+        runCurrent()
+        assertEquals(2, stage)
+        testScheduler.advanceTimeBy(1)
+        assertEquals(2, stage)
+        runCurrent()
+        assertEquals(22, stage)
+    }
+
+    /** Tests that [TestCoroutineScheduler.runCurrent] will not run new tasks after the current time has advanced. */
+    @Test
+    fun testRunCurrentNotDrainingQueue() = forTestDispatchers {
+        assertRunsFast {
+            val scheduler = it.scheduler
+            val scope = TestScope(it)
+            var stage = 1
+            scope.launch {
+                delay(SLOW)
+                launch {
+                    delay(SLOW)
+                    stage = 3
+                }
+                scheduler.advanceTimeBy(SLOW)
+                stage = 2
+            }
+            scheduler.advanceTimeBy(SLOW)
+            assertEquals(1, stage)
+            scheduler.runCurrent()
+            assertEquals(2, stage)
+            scheduler.runCurrent()
+            assertEquals(3, stage)
+        }
+    }
+
+    /** Tests that [TestCoroutineScheduler.advanceUntilIdle] doesn't hang when itself running in a scheduler task. */
+    @Test
+    fun testNestedAdvanceUntilIdle() = forTestDispatchers {
+        assertRunsFast {
+            val scheduler = it.scheduler
+            val scope = TestScope(it)
+            var executed = false
+            scope.launch {
+                launch {
+                    delay(SLOW)
+                    executed = true
+                }
+                scheduler.advanceUntilIdle()
+            }
+            scheduler.advanceUntilIdle()
+            assertTrue(executed)
+        }
+    }
+
+    /** Tests [yield] scheduling tasks for future execution and not executing immediately. */
+    @Test
+    fun testYield() = forTestDispatchers {
+        val scope = TestScope(it)
+        var stage = 0
+        scope.launch {
+            yield()
+            assertEquals(1, stage)
+            stage = 2
+        }
+        scope.launch {
+            yield()
+            assertEquals(2, stage)
+            stage = 3
+        }
+        assertEquals(0, stage)
+        stage = 1
+        scope.runCurrent()
+    }
+
+    /** Tests that dispatching the delayed tasks is ordered by their waking times. */
+    @Test
+    fun testDelaysPriority() = forTestDispatchers {
+        val scope = TestScope(it)
+        var lastMeasurement = 0L
+        fun checkTime(time: Long) {
+            assertTrue(lastMeasurement < time)
+            assertEquals(time, scope.currentTime)
+            lastMeasurement = scope.currentTime
+        }
+        scope.launch {
+            launch {
+                delay(100)
+                checkTime(100)
+                val deferred = async {
+                    delay(70)
+                    checkTime(170)
+                }
+                delay(1)
+                checkTime(101)
+                deferred.await()
+                delay(1)
+                checkTime(171)
+            }
+            launch {
+                delay(200)
+                checkTime(200)
+            }
+            launch {
+                delay(150)
+                checkTime(150)
+                delay(22)
+                checkTime(172)
+            }
+            delay(201)
+        }
+        scope.advanceUntilIdle()
+        checkTime(201)
+    }
+
+    private fun TestScope.checkTimeout(
+        timesOut: Boolean, timeoutMillis: Long = SLOW, block: suspend () -> Unit
+    ) = assertRunsFast {
+        var caughtException = false
+        asSpecificImplementation().enter()
+        launch {
+            try {
+                withTimeout(timeoutMillis) {
+                    block()
+                }
+            } catch (e: TimeoutCancellationException) {
+                caughtException = true
+            }
+        }
+        advanceUntilIdle()
+        asSpecificImplementation().leave().throwAll()
+        if (timesOut)
+            assertTrue(caughtException)
+        else
+            assertFalse(caughtException)
+    }
+
+    /** Tests that timeouts get triggered. */
+    @Test
+    fun testSmallTimeouts() = forTestDispatchers {
+        val scope = TestScope(it)
+        scope.checkTimeout(true) {
+            val half = SLOW / 2
+            delay(half)
+            delay(SLOW - half)
+        }
+    }
+
+    /** Tests that timeouts don't get triggered if the code finishes in time. */
+    @Test
+    fun testLargeTimeouts() = forTestDispatchers {
+        val scope = TestScope(it)
+        scope.checkTimeout(false) {
+            val half = SLOW / 2
+            delay(half)
+            delay(SLOW - half - 1)
+        }
+    }
+
+    /** Tests that timeouts get triggered if the code fails to finish in time asynchronously. */
+    @Test
+    fun testSmallAsynchronousTimeouts() = forTestDispatchers {
+        val scope = TestScope(it)
+        val deferred = CompletableDeferred<Unit>()
+        scope.launch {
+            val half = SLOW / 2
+            delay(half)
+            delay(SLOW - half)
+            deferred.complete(Unit)
+        }
+        scope.checkTimeout(true) {
+            deferred.await()
+        }
+    }
+
+    /** Tests that timeouts don't get triggered if the code finishes in time, even if it does so asynchronously. */
+    @Test
+    fun testLargeAsynchronousTimeouts() = forTestDispatchers {
+        val scope = TestScope(it)
+        val deferred = CompletableDeferred<Unit>()
+        scope.launch {
+            val half = SLOW / 2
+            delay(half)
+            delay(SLOW - half - 1)
+            deferred.complete(Unit)
+        }
+        scope.checkTimeout(false) {
+            deferred.await()
+        }
+    }
+
+    @Test
+    @ExperimentalTime
+    fun testAdvanceTimeSource() = runTest {
+        val expected = 1.seconds
+        val actual = testTimeSource.measureTime {
+            delay(expected)
+        }
+        assertEquals(expected, actual)
+    }
+
+    private fun forTestDispatchers(block: (TestDispatcher) -> Unit): Unit =
+        @Suppress("DEPRECATION")
+        listOf(
+            StandardTestDispatcher(),
+            UnconfinedTestDispatcher()
+        ).forEach {
+            try {
+                block(it)
+            } catch (e: Throwable) {
+                throw RuntimeException("Test failed for dispatcher $it", e)
+            }
+        }
+}
diff --git a/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt
new file mode 100644
index 0000000..bcf016b
--- /dev/null
+++ b/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016-2021 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.test.internal.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+@NoNative
+class TestDispatchersTest: OrderedExecutionTestBase() {
+
+    @BeforeTest
+    fun setUp() {
+        Dispatchers.setMain(StandardTestDispatcher())
+    }
+
+    @AfterTest
+    fun tearDown() {
+        Dispatchers.resetMain()
+    }
+
+    /** Tests that asynchronous execution of tests does not happen concurrently with [AfterTest]. */
+    @Test
+    fun testMainMocking() = runTest {
+        val mainAtStart = TestMainDispatcher.currentTestDispatcher
+        assertNotNull(mainAtStart)
+        withContext(Dispatchers.Main) {
+            delay(10)
+        }
+        withContext(Dispatchers.Default) {
+            delay(10)
+        }
+        withContext(Dispatchers.Main) {
+            delay(10)
+        }
+        assertSame(mainAtStart, TestMainDispatcher.currentTestDispatcher)
+    }
+
+    /** Tests that the mocked [Dispatchers.Main] correctly forwards [Delay] methods. */
+    @Test
+    fun testMockedMainImplementsDelay() = runTest {
+        val main = Dispatchers.Main
+        withContext(main) {
+            delay(10)
+        }
+        withContext(Dispatchers.Default) {
+            delay(10)
+        }
+        withContext(main) {
+            delay(10)
+        }
+    }
+
+    /** Tests that [Distpachers.setMain] fails when called with [Dispatchers.Main]. */
+    @Test
+    fun testSelfSet() {
+        assertFailsWith<IllegalArgumentException> { Dispatchers.setMain(Dispatchers.Main) }
+    }
+
+    @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) = throw RuntimeException("Shouldn't be reached")
+    }
+
+    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/common/test/TestScopeTest.kt b/kotlinx-coroutines-test/common/test/TestScopeTest.kt
new file mode 100644
index 0000000..4138ca0
--- /dev/null
+++ b/kotlinx-coroutines-test/common/test/TestScopeTest.kt
@@ -0,0 +1,487 @@
+/*
+ * 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 kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class TestScopeTest {
+    /** Tests failing to create a [TestScope] with incorrect contexts. */
+    @Test
+    fun testCreateThrowsOnInvalidArguments() {
+        for (ctx in invalidContexts) {
+            assertFailsWith<IllegalArgumentException> {
+                TestScope(ctx)
+            }
+        }
+    }
+
+    /** Tests that a newly-created [TestScope] provides the correct scheduler. */
+    @Test
+    fun testCreateProvidesScheduler() {
+        // Creates a new scheduler.
+        run {
+            val scope = TestScope()
+            assertNotNull(scope.coroutineContext[TestCoroutineScheduler])
+        }
+        // Reuses the scheduler that the dispatcher is linked to.
+        run {
+            val dispatcher = StandardTestDispatcher()
+            val scope = TestScope(dispatcher)
+            assertSame(dispatcher.scheduler, scope.coroutineContext[TestCoroutineScheduler])
+        }
+        // Uses the scheduler passed to it.
+        run {
+            val scheduler = TestCoroutineScheduler()
+            val scope = TestScope(scheduler)
+            assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
+            assertSame(scheduler, (scope.coroutineContext[ContinuationInterceptor] as TestDispatcher).scheduler)
+        }
+        // Doesn't touch the passed dispatcher and the scheduler if they match.
+        run {
+            val scheduler = TestCoroutineScheduler()
+            val dispatcher = StandardTestDispatcher(scheduler)
+            val scope = TestScope(scheduler + dispatcher)
+            assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
+            assertSame(dispatcher, scope.coroutineContext[ContinuationInterceptor])
+        }
+    }
+
+    /** Part of [testCreateProvidesScheduler], disabled for Native */
+    @Test
+    @NoNative
+    fun testCreateReusesScheduler() {
+        // Reuses the scheduler of `Dispatchers.Main`
+        run {
+            val scheduler = TestCoroutineScheduler()
+            val mainDispatcher = StandardTestDispatcher(scheduler)
+            Dispatchers.setMain(mainDispatcher)
+            try {
+                val scope = TestScope()
+                assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
+                assertNotSame(mainDispatcher, scope.coroutineContext[ContinuationInterceptor])
+            } finally {
+                Dispatchers.resetMain()
+            }
+        }
+        // Does not reuse the scheduler of `Dispatchers.Main` if one is explicitly passed
+        run {
+            val mainDispatcher = StandardTestDispatcher()
+            Dispatchers.setMain(mainDispatcher)
+            try {
+                val scheduler = TestCoroutineScheduler()
+                val scope = TestScope(scheduler)
+                assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
+                assertNotSame(mainDispatcher.scheduler, scope.coroutineContext[TestCoroutineScheduler])
+                assertNotSame(mainDispatcher, scope.coroutineContext[ContinuationInterceptor])
+            } finally {
+                Dispatchers.resetMain()
+            }
+        }
+    }
+
+    /** Tests that the cleanup procedure throws if there were uncompleted delays by the end. */
+    @Test
+    fun testPresentDelaysThrowing() {
+        val scope = TestScope()
+        var result = false
+        scope.launch {
+            delay(5)
+            result = true
+        }
+        assertFalse(result)
+        scope.asSpecificImplementation().enter()
+        assertFailsWith<UncompletedCoroutinesError> { scope.asSpecificImplementation().leave() }
+        assertFalse(result)
+    }
+
+    /** Tests that the cleanup procedure throws if there were active jobs by the end. */
+    @Test
+    fun testActiveJobsThrowing() {
+        val scope = TestScope()
+        var result = false
+        val deferred = CompletableDeferred<String>()
+        scope.launch {
+            deferred.await()
+            result = true
+        }
+        assertFalse(result)
+        scope.asSpecificImplementation().enter()
+        assertFailsWith<UncompletedCoroutinesError> { scope.asSpecificImplementation().leave() }
+        assertFalse(result)
+    }
+
+    /** Tests that the cleanup procedure throws even if it detects that the job is already cancelled. */
+    @Test
+    fun testCancelledDelaysThrowing() {
+        val scope = TestScope()
+        var result = false
+        val deferred = CompletableDeferred<String>()
+        val job = scope.launch {
+            deferred.await()
+            result = true
+        }
+        job.cancel()
+        assertFalse(result)
+        scope.asSpecificImplementation().enter()
+        assertFailsWith<UncompletedCoroutinesError> { scope.asSpecificImplementation().leave() }
+        assertFalse(result)
+    }
+
+    /** Tests that uncaught exceptions are thrown at the cleanup. */
+    @Test
+    fun testGetsCancelledOnChildFailure(): TestResult {
+        val scope = TestScope()
+        val exception = TestException("test")
+        scope.launch {
+            throw exception
+        }
+        return testResultMap({
+            try {
+                it()
+                fail("should not reach")
+            } catch (e: TestException) {
+                // expected
+            }
+        }) {
+            scope.runTest {
+            }
+        }
+    }
+
+    /** Tests that, when reporting several exceptions, the first one is thrown, with the rest suppressed. */
+    @Test
+    fun testSuppressedExceptions() {
+        TestScope().apply {
+            asSpecificImplementation().enter()
+            launch(SupervisorJob()) { throw TestException("x") }
+            launch(SupervisorJob()) { throw TestException("y") }
+            launch(SupervisorJob()) { throw TestException("z") }
+            runCurrent()
+            val e = asSpecificImplementation().leave()
+            assertEquals(3, e.size)
+            assertEquals("x", e[0].message)
+            assertEquals("y", e[1].message)
+            assertEquals("z", e[2].message)
+        }
+    }
+
+    /** Tests that the background work is being run at all. */
+    @Test
+    fun testBackgroundWorkBeingRun(): TestResult = runTest {
+        var i = 0
+        var j = 0
+        backgroundScope.launch {
+            ++i
+        }
+        backgroundScope.launch {
+            delay(10)
+            ++j
+        }
+        assertEquals(0, i)
+        assertEquals(0, j)
+        delay(1)
+        assertEquals(1, i)
+        assertEquals(0, j)
+        delay(10)
+        assertEquals(1, i)
+        assertEquals(1, j)
+    }
+
+    /**
+     * Tests that the background work gets cancelled after the test body finishes.
+     */
+    @Test
+    fun testBackgroundWorkCancelled(): TestResult {
+        var cancelled = false
+        return testResultMap({
+            it()
+            assertTrue(cancelled)
+        }) {
+            runTest {
+                var i = 0
+                backgroundScope.launch {
+                    try {
+                        while (isActive) {
+                            ++i
+                            yield()
+                        }
+                    } catch (e: CancellationException) {
+                        cancelled = true
+                    }
+                }
+                repeat(5) {
+                    assertEquals(i, it)
+                    yield()
+                }
+            }
+        }
+    }
+
+    /** Tests the interactions between the time-control commands and the background work. */
+    @Test
+    fun testBackgroundWorkTimeControl(): TestResult = runTest {
+        var i = 0
+        var j = 0
+        backgroundScope.launch {
+            while (true) {
+                ++i
+                delay(100)
+            }
+        }
+        backgroundScope.launch {
+            while (true) {
+                ++j
+                delay(50)
+            }
+        }
+        advanceUntilIdle() // should do nothing, as only background work is left.
+        assertEquals(0, i)
+        assertEquals(0, j)
+        val job = launch {
+            delay(1)
+            // the background work scheduled for earlier gets executed before the normal work scheduled for later does
+            assertEquals(1, i)
+            assertEquals(1, j)
+        }
+        job.join()
+        advanceTimeBy(199) // should work the same for the background tasks
+        assertEquals(2, i)
+        assertEquals(4, j)
+        advanceUntilIdle() // once again, should do nothing
+        assertEquals(2, i)
+        assertEquals(4, j)
+        runCurrent() // should behave the same way as for the normal work
+        assertEquals(3, i)
+        assertEquals(5, j)
+        launch {
+            delay(1001)
+            assertEquals(13, i)
+            assertEquals(25, j)
+        }
+        advanceUntilIdle() // should execute the normal work, and with that, the background one, too
+    }
+
+    /**
+     * Tests that an error in a background coroutine does not cancel the test, but is reported at the end.
+     */
+    @Test
+    fun testBackgroundWorkErrorReporting(): TestResult {
+        var testFinished = false
+        val exception = RuntimeException("x")
+        return testResultMap({
+            try {
+                it()
+                fail("unreached")
+            } catch (e: Throwable) {
+                assertSame(e, exception)
+                assertTrue(testFinished)
+            }
+        }) {
+            runTest {
+                backgroundScope.launch {
+                    throw exception
+                }
+                delay(1000)
+                testFinished = true
+            }
+        }
+    }
+
+    /**
+     * Tests that the background work gets to finish what it's doing after the test is completed.
+     */
+    @Test
+    fun testBackgroundWorkFinalizing(): TestResult {
+        var taskEnded = 0
+        val nTasks = 10
+        return testResultMap({
+            try {
+                it()
+                fail("unreached")
+            } catch (e: TestException) {
+                assertEquals(2, e.suppressedExceptions.size)
+                assertEquals(nTasks, taskEnded)
+            }
+        }) {
+            runTest {
+                repeat(nTasks) {
+                    backgroundScope.launch {
+                        try {
+                            while (true) {
+                                delay(1)
+                            }
+                        } finally {
+                            ++taskEnded
+                            if (taskEnded <= 2)
+                                throw TestException()
+                        }
+                    }
+                }
+                delay(100)
+                throw TestException()
+            }
+        }
+    }
+
+    /**
+     * Tests using [Flow.stateIn] as a background job.
+     */
+    @Test
+    fun testExampleBackgroundJob1() = runTest {
+        val myFlow = flow {
+            var i = 0
+            while (true) {
+                emit(++i)
+                delay(1)
+            }
+        }
+        val stateFlow = myFlow.stateIn(backgroundScope, SharingStarted.Eagerly, 0)
+        var j = 0
+        repeat(100) {
+            assertEquals(j++, stateFlow.value)
+            delay(1)
+        }
+    }
+
+    /**
+     * A test from the documentation of [TestScope.backgroundScope].
+     */
+    @Test
+    fun testExampleBackgroundJob2() = runTest {
+        val channel = Channel<Int>()
+        backgroundScope.launch {
+            var i = 0
+            while (true) {
+                channel.send(i++)
+            }
+        }
+        repeat(100) {
+            assertEquals(it, channel.receive())
+        }
+    }
+
+    /**
+     * Tests that the test will timeout due to idleness even if some background tasks are running.
+     */
+    @Test
+    fun testBackgroundWorkNotPreventingTimeout(): TestResult = testResultMap({
+        try {
+            it()
+            fail("unreached")
+        } catch (_: UncompletedCoroutinesError) {
+
+        }
+    }) {
+        runTest(dispatchTimeoutMs = 100) {
+            backgroundScope.launch {
+                while (true) {
+                    yield()
+                }
+            }
+            backgroundScope.launch {
+                while (true) {
+                    delay(1)
+                }
+            }
+            val deferred = CompletableDeferred<Unit>()
+            deferred.await()
+        }
+
+    }
+
+    /**
+     * Tests that the background work will not prevent the test from timing out even in some cases
+     * when the unconfined dispatcher is used.
+     */
+    @Test
+    fun testUnconfinedBackgroundWorkNotPreventingTimeout(): TestResult = testResultMap({
+        try {
+            it()
+            fail("unreached")
+        } catch (_: UncompletedCoroutinesError) {
+
+        }
+    }) {
+        runTest(UnconfinedTestDispatcher(), dispatchTimeoutMs = 100) {
+            /**
+             * Having a coroutine like this will still cause the test to hang:
+                 backgroundScope.launch {
+                     while (true) {
+                         yield()
+                     }
+                 }
+             * The reason is that even the initial [advanceUntilIdle] will never return in this case.
+             */
+            backgroundScope.launch {
+                while (true) {
+                    delay(1)
+                }
+            }
+            val deferred = CompletableDeferred<Unit>()
+            deferred.await()
+        }
+    }
+
+    /**
+     * Tests that even the exceptions in the background scope that don't typically get reported and need to be queried
+     * (like failures in [async]) will still surface in some simple scenarios.
+     */
+    @Test
+    fun testAsyncFailureInBackgroundReported() = testResultMap({
+        try {
+            it()
+            fail("unreached")
+        } catch (e: TestException) {
+            assertEquals("z", e.message)
+            assertEquals(setOf("x", "y"), e.suppressedExceptions.map { it.message }.toSet())
+        }
+    }) {
+        runTest {
+            backgroundScope.async {
+                throw TestException("x")
+            }
+            backgroundScope.produce<Unit> {
+                throw TestException("y")
+            }
+            delay(1)
+            throw TestException("z")
+        }
+    }
+
+    /**
+     * Tests that, if an exception reaches the [TestScope] exception reporting mechanism via several
+     * channels, it will only be reported once.
+     */
+    @Test
+    fun testNoDuplicateExceptions() = testResultMap({
+        try {
+            it()
+            fail("unreached")
+        } catch (e: TestException) {
+            assertEquals("y", e.message)
+            assertEquals(listOf("x"), e.suppressedExceptions.map { it.message })
+        }
+    }) {
+        runTest {
+            backgroundScope.launch {
+                throw TestException("x")
+            }
+            delay(1)
+            throw TestException("y")
+        }
+    }
+
+    companion object {
+        internal val invalidContexts = listOf(
+            Dispatchers.Default, // not a [TestDispatcher]
+            CoroutineExceptionHandler { _, _ -> }, // exception handlers can't be overridden
+            StandardTestDispatcher() + TestCoroutineScheduler(), // the dispatcher is not linked to the scheduler
+        )
+    }
+}
diff --git a/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt b/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt
new file mode 100644
index 0000000..ee63e6d
--- /dev/null
+++ b/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2016-2021 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.channels.*
+import kotlinx.coroutines.flow.*
+import kotlin.test.*
+
+class UnconfinedTestDispatcherTest {
+
+    @Test
+    fun reproducer1742() {
+        class ObservableValue<T>(initial: T) {
+            var value: T = initial
+                private set
+
+            private val listeners = mutableListOf<(T) -> Unit>()
+
+            fun set(value: T) {
+                this.value = value
+                listeners.forEach { it(value) }
+            }
+
+            fun addListener(listener: (T) -> Unit) {
+                listeners.add(listener)
+            }
+
+            fun removeListener(listener: (T) -> Unit) {
+                listeners.remove(listener)
+            }
+        }
+
+        fun <T> ObservableValue<T>.observe(): Flow<T> =
+            callbackFlow {
+                val listener = { value: T ->
+                    if (!isClosedForSend) {
+                        trySend(value)
+                    }
+                }
+                addListener(listener)
+                listener(value)
+                awaitClose { removeListener(listener) }
+            }
+
+        val intProvider = ObservableValue(0)
+        val stringProvider = ObservableValue("")
+        var data = Pair(0, "")
+        val scope = CoroutineScope(UnconfinedTestDispatcher())
+        scope.launch {
+            combine(
+                intProvider.observe(),
+                stringProvider.observe()
+            ) { intValue, stringValue -> Pair(intValue, stringValue) }
+                .collect { pair ->
+                    data = pair
+                }
+        }
+
+        intProvider.set(1)
+        stringProvider.set("3")
+        intProvider.set(2)
+        intProvider.set(3)
+
+        scope.cancel()
+        assertEquals(Pair(3, "3"), data)
+    }
+
+    @Test
+    fun reproducer2082() = runTest {
+        val subject1 = MutableStateFlow(1)
+        val subject2 = MutableStateFlow("a")
+        val values = mutableListOf<Pair<Int, String>>()
+
+        val job = launch(UnconfinedTestDispatcher(testScheduler)) {
+            combine(subject1, subject2) { intVal, strVal -> intVal to strVal }
+                .collect {
+                    delay(10000)
+                    values += it
+                }
+        }
+
+        subject1.value = 2
+        delay(10000)
+        subject2.value = "b"
+        delay(10000)
+
+        subject1.value = 3
+        delay(10000)
+        subject2.value = "c"
+        delay(10000)
+        delay(10000)
+        delay(1)
+
+        job.cancel()
+
+        assertEquals(listOf(Pair(1, "a"), Pair(2, "a"), Pair(2, "b"), Pair(3, "b"), Pair(3, "c")), values)
+    }
+
+    @Test
+    fun reproducer2405() = createTestResult {
+        val dispatcher = UnconfinedTestDispatcher()
+        var collectedError = false
+        withContext(dispatcher) {
+            flow { emit(1) }
+                .combine(
+                    flow<String> { throw IllegalArgumentException() }
+                ) { int, string -> int.toString() + string }
+                .catch { emit("error") }
+                .collect {
+                    assertEquals("error", it)
+                    collectedError = true
+                }
+        }
+        assertTrue(collectedError)
+    }
+
+    /** An example from the [UnconfinedTestDispatcher] documentation. */
+    @Test
+    fun testUnconfinedDispatcher() = runTest {
+        val values = mutableListOf<Int>()
+        val stateFlow = MutableStateFlow(0)
+        val job = launch(UnconfinedTestDispatcher(testScheduler)) {
+            stateFlow.collect {
+                values.add(it)
+            }
+        }
+        stateFlow.value = 1
+        stateFlow.value = 2
+        stateFlow.value = 3
+        job.cancel()
+        assertEquals(listOf(0, 1, 2, 3), values)
+    }
+
+    /** Tests that child coroutines are eagerly entered. */
+    @Test
+    fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
+        var entered = false
+        val deferred = CompletableDeferred<Unit>()
+        var completed = false
+        launch {
+            entered = true
+            deferred.await()
+            completed = true
+        }
+        assertTrue(entered) // `entered = true` already executed.
+        assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued.
+        deferred.complete(Unit) // resume the coroutine.
+        assertTrue(completed) // now the child coroutine is immediately completed.
+    }
+
+    /** Tests that the [TestCoroutineScheduler] used for [Dispatchers.Main] gets used by default. */
+    @Test
+    @NoNative
+    fun testSchedulerReuse() {
+        val dispatcher1 = StandardTestDispatcher()
+        Dispatchers.setMain(dispatcher1)
+        try {
+            val dispatcher2 = UnconfinedTestDispatcher()
+            assertSame(dispatcher1.scheduler, dispatcher2.scheduler)
+        } finally {
+            Dispatchers.resetMain()
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/js/src/TestBuilders.kt b/kotlinx-coroutines-test/js/src/TestBuilders.kt
new file mode 100644
index 0000000..9da91ff
--- /dev/null
+++ b/kotlinx-coroutines-test/js/src/TestBuilders.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2021 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.js.*
+
+@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE")
+public actual typealias TestResult = Promise<Unit>
+
+internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> Unit): TestResult =
+    GlobalScope.promise {
+        testProcedure()
+    }
diff --git a/kotlinx-coroutines-test/js/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/js/src/internal/TestMainDispatcher.kt
new file mode 100644
index 0000000..4d865f8
--- /dev/null
+++ b/kotlinx-coroutines-test/js/src/internal/TestMainDispatcher.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test.internal
+import kotlinx.coroutines.*
+
+@Suppress("INVISIBLE_MEMBER")
+internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher =
+    when (val mainDispatcher = Main) {
+        is TestMainDispatcher -> mainDispatcher
+        else -> TestMainDispatcher(mainDispatcher).also { injectMain(it) }
+    }
diff --git a/kotlinx-coroutines-test/js/test/Helpers.kt b/kotlinx-coroutines-test/js/test/Helpers.kt
new file mode 100644
index 0000000..5f19d1a
--- /dev/null
+++ b/kotlinx-coroutines-test/js/test/Helpers.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+
+import kotlin.test.*
+
+actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult =
+    test().then(
+        {
+            block {
+            }
+        }, {
+            block {
+                throw it
+            }
+        })
+
+actual typealias NoJs = Ignore
diff --git a/kotlinx-coroutines-test/js/test/PromiseTest.kt b/kotlinx-coroutines-test/js/test/PromiseTest.kt
new file mode 100644
index 0000000..ff09d9a
--- /dev/null
+++ b/kotlinx-coroutines-test/js/test/PromiseTest.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class PromiseTest {
+    @Test
+    fun testCompletionFromPromise() = runTest {
+        var promiseEntered = false
+        val p = promise {
+            delay(1)
+            promiseEntered = true
+        }
+        delay(2)
+        p.await()
+        assertTrue(promiseEntered)
+    }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-test/jvm/resources/META-INF/proguard/coroutines.pro
similarity index 100%
rename from kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro
rename to kotlinx-coroutines-test/jvm/resources/META-INF/proguard/coroutines.pro
diff --git a/kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory b/kotlinx-coroutines-test/jvm/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
similarity index 100%
rename from kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
rename to kotlinx-coroutines-test/jvm/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
diff --git a/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt b/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt
new file mode 100644
index 0000000..06fbe81
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual typealias TestResult = Unit
+
+internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> Unit) {
+    runBlocking {
+        testProcedure()
+    }
+}
diff --git a/kotlinx-coroutines-test/jvm/src/internal/TestMainDispatcherJvm.kt b/kotlinx-coroutines-test/jvm/src/internal/TestMainDispatcherJvm.kt
new file mode 100644
index 0000000..f86b08e
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/src/internal/TestMainDispatcherJvm.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test.internal
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+
+internal class TestMainDispatcherFactory : MainDispatcherFactory {
+
+    override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
+        val otherFactories = allFactories.filter { it !== this }
+        val secondBestFactory = otherFactories.maxByOrNull { it.loadPriority } ?: MissingMainCoroutineDispatcherFactory
+        val dispatcher = secondBestFactory.tryCreateDispatcher(otherFactories)
+        return TestMainDispatcher(dispatcher)
+    }
+
+    /**
+     * [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
+}
+
+internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher {
+    val mainDispatcher = Main
+    require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." }
+    return mainDispatcher
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/jvm/src/migration/DelayController.kt b/kotlinx-coroutines-test/jvm/src/migration/DelayController.kt
new file mode 100644
index 0000000..3ccf2ca
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/src/migration/DelayController.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("DEPRECATION")
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+
+/**
+ * Control the virtual clock time of a [CoroutineDispatcher].
+ *
+ * Testing libraries may expose this interface to the tests instead of [TestCoroutineDispatcher].
+ *
+ * This interface is deprecated without replacement.
+ * Instead, [TestCoroutineScheduler] is supposed to be used to control the virtual time.
+ * Please see the
+ * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
+ * for an instruction on how to update the code for the new API.
+ */
+@ExperimentalCoroutinesApi
+@Deprecated(
+    "Use `TestCoroutineScheduler` to control virtual time.",
+    level = DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public interface DelayController {
+    /**
+     * Returns the current virtual clock-time as it is known to this Dispatcher.
+     *
+     * @return The virtual clock-time
+     */
+    @ExperimentalCoroutinesApi
+    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
+    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
+    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
+    public fun runCurrent()
+
+    /**
+     * Call after test code completes to ensure that the dispatcher is properly cleaned up.
+     *
+     * @throws AssertionError if any pending tasks are active, however it will not throw for suspended
+     * coroutines.
+     */
+    @ExperimentalCoroutinesApi
+    @Throws(AssertionError::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.
+     */
+    @Deprecated(
+        "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
+        level = DeprecationLevel.WARNING
+    )
+    // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.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.
+     */
+    @Deprecated(
+        "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
+        level = DeprecationLevel.WARNING
+    )
+    // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.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].
+     */
+    @Deprecated(
+        "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
+        level = DeprecationLevel.WARNING
+    )
+    // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+    public fun resumeDispatcher()
+}
+
+internal interface SchedulerAsDelayController : DelayController {
+    val scheduler: TestCoroutineScheduler
+
+    /** @suppress */
+    @Deprecated(
+        "This property delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
+        ReplaceWith("this.scheduler.currentTime"),
+        level = DeprecationLevel.WARNING
+    )
+    // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+    override val currentTime: Long
+        get() = scheduler.currentTime
+
+
+    /** @suppress */
+    @Deprecated(
+        "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
+        ReplaceWith("this.scheduler.apply { advanceTimeBy(delayTimeMillis); runCurrent() }"),
+        level = DeprecationLevel.WARNING
+    )
+    // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+    override fun advanceTimeBy(delayTimeMillis: Long): Long {
+        val oldTime = scheduler.currentTime
+        scheduler.advanceTimeBy(delayTimeMillis)
+        scheduler.runCurrent()
+        return scheduler.currentTime - oldTime
+    }
+
+    /** @suppress */
+    @Deprecated(
+        "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
+        ReplaceWith("this.scheduler.advanceUntilIdle()"),
+        level = DeprecationLevel.WARNING
+    )
+    // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+    override fun advanceUntilIdle(): Long {
+        val oldTime = scheduler.currentTime
+        scheduler.advanceUntilIdle()
+        return scheduler.currentTime - oldTime
+    }
+
+    /** @suppress */
+    @Deprecated(
+        "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
+        ReplaceWith("this.scheduler.runCurrent()"),
+        level = DeprecationLevel.WARNING
+    )
+    // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+    override fun runCurrent(): Unit = scheduler.runCurrent()
+
+    /** @suppress */
+    @ExperimentalCoroutinesApi
+    override fun cleanupTestCoroutines() {
+        // process any pending cancellations or completions, but don't advance time
+        scheduler.runCurrent()
+        if (!scheduler.isIdle(strict = false)) {
+            throw UncompletedCoroutinesError(
+                "Unfinished coroutines during tear-down. Ensure all coroutines are" +
+                    " completed or cancelled by your test."
+            )
+        }
+    }
+}
diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt
new file mode 100644
index 0000000..eabdffb
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("DEPRECATION")
+@file:JvmName("TestBuildersKt")
+@file:JvmMultifileClass
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/**
+ * Executes a [testBody] inside an immediate execution dispatcher.
+ *
+ * This method is deprecated in favor of [runTest]. Please see the
+ * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
+ * for an instruction on how to update the code for the new API.
+ *
+ * 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 AssertionError 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.
+ */
+@Deprecated("Use `runTest` instead to support completing from other dispatchers. " +
+    "Please see the migration guide for details: " +
+    "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
+    level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun runBlockingTest(
+    context: CoroutineContext = EmptyCoroutineContext,
+    testBody: suspend TestCoroutineScope.() -> Unit
+) {
+    val scope = createTestCoroutineScope(TestCoroutineDispatcher() + SupervisorJob() + context)
+    val scheduler = scope.testScheduler
+    val deferred = scope.async {
+        scope.testBody()
+    }
+    scheduler.advanceUntilIdle()
+    deferred.getCompletionExceptionOrNull()?.let {
+        throw it
+    }
+    scope.cleanupTestCoroutines()
+}
+
+/**
+ * A version of [runBlockingTest] that works with [TestScope].
+ */
+@Deprecated("Use `runTest` instead to support completing from other dispatchers.", level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun runBlockingTestOnTestScope(
+    context: CoroutineContext = EmptyCoroutineContext,
+    testBody: suspend TestScope.() -> Unit
+) {
+    val completeContext = TestCoroutineDispatcher() + SupervisorJob() + context
+    val startJobs = completeContext.activeJobs()
+    val scope = TestScope(completeContext).asSpecificImplementation()
+    scope.enter()
+    scope.start(CoroutineStart.UNDISPATCHED, scope) {
+        scope.testBody()
+    }
+    scope.testScheduler.advanceUntilIdle()
+    val throwable = try {
+        scope.getCompletionExceptionOrNull()
+    } catch (e: IllegalStateException) {
+        null // the deferred was not completed yet; `scope.leave()` should complain then about unfinished jobs
+    }
+    scope.backgroundScope.cancel()
+    scope.testScheduler.advanceUntilIdleOr { false }
+    throwable?.let {
+        val exceptions = try {
+            scope.leave()
+        } catch (e: UncompletedCoroutinesError) {
+            listOf()
+        }
+        (listOf(it) + exceptions).throwAll()
+        return
+    }
+    scope.leave().throwAll()
+    val jobs = completeContext.activeJobs() - startJobs
+    if (jobs.isNotEmpty())
+        throw UncompletedCoroutinesError("Some jobs were not completed at the end of the test: $jobs")
+}
+
+/**
+ * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineScope].
+ *
+ * This method is deprecated in favor of [runTest], whereas [TestCoroutineScope] is deprecated in favor of [TestScope].
+ * Please see the
+ * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
+ * for an instruction on how to update the code for the new API.
+ */
+@Deprecated("Use `runTest` instead to support completing from other dispatchers. " +
+    "Please see the migration guide for details: " +
+    "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
+    level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun TestCoroutineScope.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit =
+    runBlockingTest(coroutineContext, block)
+
+/**
+ * Convenience method for calling [runBlockingTestOnTestScope] on an existing [TestScope].
+ */
+@Deprecated("Use `runTest` instead to support completing from other dispatchers.", level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun TestScope.runBlockingTest(block: suspend TestScope.() -> Unit): Unit =
+    runBlockingTestOnTestScope(coroutineContext, block)
+
+/**
+ * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineDispatcher].
+ *
+ * This method is deprecated in favor of [runTest], whereas [TestCoroutineScope] is deprecated in favor of [TestScope].
+ * Please see the
+ * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
+ * for an instruction on how to update the code for the new API.
+ */
+@Deprecated("Use `runTest` instead to support completing from other dispatchers. " +
+    "Please see the migration guide for details: " +
+    "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
+    level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit =
+    runBlockingTest(this, block)
+
+/**
+ * This is an overload of [runTest] that works with [TestCoroutineScope].
+ */
+@ExperimentalCoroutinesApi
+@Deprecated("Use `runTest` instead.", level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun runTestWithLegacyScope(
+    context: CoroutineContext = EmptyCoroutineContext,
+    dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
+    testBody: suspend TestCoroutineScope.() -> Unit
+): TestResult {
+    if (context[RunningInRunTest] != null)
+        throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.")
+    val testScope = TestBodyCoroutine(createTestCoroutineScope(context + RunningInRunTest))
+    return createTestResult {
+        runTestCoroutine(testScope, dispatchTimeoutMs, TestBodyCoroutine::tryGetCompletionCause, testBody) {
+            try {
+                testScope.cleanup()
+                emptyList()
+            } catch (e: UncompletedCoroutinesError) {
+                throw e
+            } catch (e: Throwable) {
+                listOf(e)
+            }
+        }
+    }
+}
+
+/**
+ * Runs a test in a [TestCoroutineScope] based on this one.
+ *
+ * Calls [runTest] using a coroutine context from this [TestCoroutineScope]. The [TestCoroutineScope] used to run the
+ * [block] will be different from this one, but will use its [Job] as a parent.
+ *
+ * Since this function returns [TestResult], in order to work correctly on the JS, its result must be returned
+ * immediately from the test body. See the docs for [TestResult] for details.
+ */
+@ExperimentalCoroutinesApi
+@Deprecated("Use `TestScope.runTest` instead.", level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun TestCoroutineScope.runTest(
+    dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
+    block: suspend TestCoroutineScope.() -> Unit
+): TestResult = runTestWithLegacyScope(coroutineContext, dispatchTimeoutMs, block)
+
+private class TestBodyCoroutine(
+    private val testScope: TestCoroutineScope,
+) : AbstractCoroutine<Unit>(testScope.coroutineContext, initParentJob = true, active = true), TestCoroutineScope {
+
+    override val testScheduler get() = testScope.testScheduler
+
+    @Deprecated(
+        "This deprecation is to prevent accidentally calling `cleanupTestCoroutines` in our own code.",
+        ReplaceWith("this.cleanup()"),
+        DeprecationLevel.ERROR
+    )
+    override fun cleanupTestCoroutines() =
+        throw UnsupportedOperationException(
+            "Calling `cleanupTestCoroutines` inside `runTest` is prohibited: " +
+                "it will be called at the end of the test in any case."
+        )
+
+    fun cleanup() = testScope.cleanupTestCoroutines()
+
+    /** Throws an exception if the coroutine is not completing. */
+    fun tryGetCompletionCause(): Throwable? = completionCause
+}
diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt
new file mode 100644
index 0000000..08f428f
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016-2021 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.*
+
+/**
+ * [CoroutineDispatcher] that performs both immediate and lazy execution of coroutines in tests
+ * and uses a [TestCoroutineScheduler] 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 the dispatcher's [scheduler].
+ *
+ * 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
+ */
+@Deprecated("The execution order of `TestCoroutineDispatcher` can be confusing, and the mechanism of " +
+    "pausing is typically misunderstood. Please use `StandardTestDispatcher` or `UnconfinedTestDispatcher` instead.",
+    level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public class TestCoroutineDispatcher(public override val scheduler: TestCoroutineScheduler = TestCoroutineScheduler()):
+    TestDispatcher(), Delay, SchedulerAsDelayController
+{
+    private var dispatchImmediately = true
+        set(value) {
+            field = value
+            if (value) {
+                // there may already be tasks from setup code we need to run
+                scheduler.advanceUntilIdle()
+            }
+        }
+
+    /** @suppress */
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        checkSchedulerInContext(scheduler, context)
+        if (dispatchImmediately) {
+            scheduler.sendDispatchEvent(context)
+            block.run()
+        } else {
+            post(block, context)
+        }
+    }
+
+    /** @suppress */
+    override fun dispatchYield(context: CoroutineContext, block: Runnable) {
+        checkSchedulerInContext(scheduler, context)
+        post(block, context)
+    }
+
+    /** @suppress */
+    override fun toString(): String = "TestCoroutineDispatcher[scheduler=$scheduler]"
+
+    private fun post(block: Runnable, context: CoroutineContext) =
+        scheduler.registerEvent(this, 0, block, context) { false }
+
+    /** @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
+    }
+}
diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt
new file mode 100644
index 0000000..9da521f
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016-2021 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.internal.*
+import kotlin.coroutines.*
+
+/**
+ * Access uncaught coroutine exceptions captured during test execution.
+ */
+@Deprecated(
+    "Deprecated for removal without a replacement. " +
+        "Consider whether the default mechanism of handling uncaught exceptions is sufficient. " +
+        "If not, try writing your own `CoroutineExceptionHandler` and " +
+        "please report your use case at https://github.com/Kotlin/kotlinx.coroutines/issues.",
+    level = DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.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.
+ */
+@Deprecated(
+    "Deprecated for removal without a replacement. " +
+        "It may be to define one's own `CoroutineExceptionHandler` if you just need to handle '" +
+        "uncaught exceptions without a special `TestCoroutineScope` integration.", level = DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public class TestCoroutineExceptionHandler :
+    AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler, UncaughtExceptionCaptor {
+    private val _exceptions = mutableListOf<Throwable>()
+    private val _lock = SynchronizedObject()
+    private var _coroutinesCleanedUp = false
+
+    @Suppress("INVISIBLE_MEMBER")
+    override fun handleException(context: CoroutineContext, exception: Throwable) {
+        synchronized(_lock) {
+            if (_coroutinesCleanedUp) {
+                handleCoroutineExceptionImpl(context, exception)
+            }
+            _exceptions += exception
+        }
+    }
+
+    public override val uncaughtExceptions: List<Throwable>
+        get() = synchronized(_lock) { _exceptions.toList() }
+
+    public override fun cleanupTestCoroutines() {
+        synchronized(_lock) {
+            _coroutinesCleanedUp = true
+            val exception = _exceptions.firstOrNull() ?: return
+            // log the rest
+            _exceptions.drop(1).forEach { it.printStackTrace() }
+            throw exception
+        }
+    }
+}
diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt
new file mode 100644
index 0000000..4a2cbc5
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("DEPRECATION")
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+
+/**
+ * A scope which provides detailed control over the execution of coroutines for tests.
+ *
+ * This scope is deprecated in favor of [TestScope].
+ * Please see the
+ * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
+ * for an instruction on how to update the code for the new API.
+ */
+@ExperimentalCoroutinesApi
+@Deprecated("Use `TestScope` in combination with `runTest` instead." +
+    "Please see the migration guide for details: " +
+    "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
+    level = DeprecationLevel.WARNING)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public interface TestCoroutineScope : CoroutineScope {
+    /**
+     * Called after the test completes.
+     *
+     * * It checks that there were no uncaught exceptions caught by its [CoroutineExceptionHandler].
+     *   If there were any, then the first one is thrown, whereas the rest are suppressed by it.
+     * * It runs the tasks pending in the scheduler at the current time. If there are any uncompleted tasks afterwards,
+     *   it fails with [UncompletedCoroutinesError].
+     * * It checks whether some new child [Job]s were created but not completed since this [TestCoroutineScope] was
+     *   created. If so, it fails with [UncompletedCoroutinesError].
+     *
+     * For backward compatibility, if the [CoroutineExceptionHandler] is an [UncaughtExceptionCaptor], its
+     * [TestCoroutineExceptionHandler.cleanupTestCoroutines] behavior is performed.
+     * Likewise, if the [ContinuationInterceptor] is a [DelayController], its [DelayController.cleanupTestCoroutines]
+     * is called.
+     *
+     * @throws Throwable the first uncaught exception, if there are any uncaught exceptions.
+     * @throws AssertionError if any pending tasks are active.
+     * @throws IllegalStateException if called more than once.
+     */
+    @ExperimentalCoroutinesApi
+    @Deprecated("Please call `runTest`, which automatically performs the cleanup, instead of using this function.")
+    // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+    public fun cleanupTestCoroutines()
+
+    /**
+     * The delay-skipping scheduler used by the test dispatchers running the code in this scope.
+     */
+    @ExperimentalCoroutinesApi
+    public val testScheduler: TestCoroutineScheduler
+}
+
+private class TestCoroutineScopeImpl(
+    override val coroutineContext: CoroutineContext
+) : TestCoroutineScope {
+    private val lock = SynchronizedObject()
+    private var exceptions = mutableListOf<Throwable>()
+    private var cleanedUp = false
+
+    /**
+     * Reports an exception so that it is thrown on [cleanupTestCoroutines].
+     *
+     * If several exceptions are reported, only the first one will be thrown, and the other ones will be suppressed by
+     * it.
+     *
+     * Returns `false` if [cleanupTestCoroutines] was already called.
+     */
+    fun reportException(throwable: Throwable): Boolean =
+        synchronized(lock) {
+            if (cleanedUp) {
+                false
+            } else {
+                exceptions.add(throwable)
+                true
+            }
+        }
+
+    override val testScheduler: TestCoroutineScheduler
+        get() = coroutineContext[TestCoroutineScheduler]!!
+
+    /** These jobs existed before the coroutine scope was used, so it's alright if they don't get cancelled. */
+    private val initialJobs = coroutineContext.activeJobs()
+
+    override fun cleanupTestCoroutines() {
+        val delayController = coroutineContext.delayController
+        val hasUnfinishedJobs = if (delayController != null) {
+            try {
+                delayController.cleanupTestCoroutines()
+                false
+            } catch (e: UncompletedCoroutinesError) {
+                true
+            }
+        } else {
+            testScheduler.runCurrent()
+            !testScheduler.isIdle(strict = false)
+        }
+        (coroutineContext[CoroutineExceptionHandler] as? UncaughtExceptionCaptor)?.cleanupTestCoroutines()
+        synchronized(lock) {
+            if (cleanedUp)
+                throw IllegalStateException("Attempting to clean up a test coroutine scope more than once.")
+            cleanedUp = true
+        }
+        exceptions.firstOrNull()?.let { toThrow ->
+            exceptions.drop(1).forEach { toThrow.addSuppressed(it) }
+            throw toThrow
+        }
+        if (hasUnfinishedJobs)
+            throw UncompletedCoroutinesError(
+                "Unfinished coroutines during teardown. Ensure all coroutines are" +
+                    " completed or cancelled by your test."
+            )
+        val jobs = coroutineContext.activeJobs()
+        if ((jobs - initialJobs).isNotEmpty())
+            throw UncompletedCoroutinesError("Test finished with active jobs: $jobs")
+    }
+}
+
+internal fun CoroutineContext.activeJobs(): Set<Job> {
+    return checkNotNull(this[Job]).children.filter { it.isActive }.toSet()
+}
+
+/**
+ * A coroutine scope for launching test coroutines using [TestCoroutineDispatcher].
+ *
+ * [createTestCoroutineScope] is a similar function that defaults to [StandardTestDispatcher].
+ */
+@Deprecated(
+    "This constructs a `TestCoroutineScope` with a deprecated `CoroutineDispatcher` by default. " +
+        "Please use `createTestCoroutineScope` instead.",
+    ReplaceWith(
+        "createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + context)",
+        "kotlin.coroutines.EmptyCoroutineContext"
+    ),
+    level = DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun TestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext): TestCoroutineScope {
+    val scheduler = context[TestCoroutineScheduler] ?: TestCoroutineScheduler()
+    return createTestCoroutineScope(TestCoroutineDispatcher(scheduler) + TestCoroutineExceptionHandler() + context)
+}
+
+/**
+ * A coroutine scope for launching test coroutines.
+ *
+ * This is a function for aiding in migration from [TestCoroutineScope] to [TestScope].
+ * Please see the
+ * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
+ * for an instruction on how to update the code for the new API.
+ *
+ * It ensures that all the test module machinery is properly initialized.
+ * * If [context] doesn't define a [TestCoroutineScheduler] for orchestrating the virtual time used for delay-skipping,
+ *   a new one is created, unless either
+ *   - a [TestDispatcher] is provided, in which case [TestDispatcher.scheduler] is used;
+ *   - at the moment of the creation of the scope, [Dispatchers.Main] is delegated to a [TestDispatcher], in which case
+ *     its [TestCoroutineScheduler] is used.
+ * * If [context] doesn't have a [ContinuationInterceptor], a [StandardTestDispatcher] is created.
+ * * A [CoroutineExceptionHandler] is created that makes [TestCoroutineScope.cleanupTestCoroutines] throw if there were
+ *   any uncaught exceptions, or forwards the exceptions further in a platform-specific manner if the cleanup was
+ *   already performed when an exception happened. Passing a [CoroutineExceptionHandler] is illegal, unless it's an
+ *   [UncaughtExceptionCaptor], in which case the behavior is preserved for the time being for backward compatibility.
+ *   If you need to have a specific [CoroutineExceptionHandler], please pass it to [launch] on an already-created
+ *   [TestCoroutineScope] and share your use case at
+ *   [our issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues).
+ * * If [context] provides a [Job], that job is used for the new scope; otherwise, a [CompletableJob] is created.
+ *
+ * @throws IllegalArgumentException if [context] has both [TestCoroutineScheduler] and a [TestDispatcher] linked to a
+ * different scheduler.
+ * @throws IllegalArgumentException if [context] has a [ContinuationInterceptor] that is not a [TestDispatcher].
+ * @throws IllegalArgumentException if [context] has an [CoroutineExceptionHandler] that is not an
+ * [UncaughtExceptionCaptor].
+ */
+@ExperimentalCoroutinesApi
+@Deprecated(
+    "This function was introduced in order to help migrate from TestCoroutineScope to TestScope. " +
+        "Please use TestScope() construction instead, or just runTest(), without creating a scope.",
+    level = DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun createTestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext): TestCoroutineScope {
+    val ctxWithDispatcher = context.withDelaySkipping()
+    var scope: TestCoroutineScopeImpl? = null
+    val ownExceptionHandler =
+        object : AbstractCoroutineContextElement(CoroutineExceptionHandler), TestCoroutineScopeExceptionHandler {
+            override fun handleException(context: CoroutineContext, exception: Throwable) {
+                if (!scope!!.reportException(exception))
+                    throw exception // let this exception crash everything
+            }
+        }
+    val exceptionHandler = when (val exceptionHandler = ctxWithDispatcher[CoroutineExceptionHandler]) {
+        is UncaughtExceptionCaptor -> exceptionHandler
+        null -> ownExceptionHandler
+        is TestCoroutineScopeExceptionHandler -> ownExceptionHandler
+        else -> throw IllegalArgumentException(
+            "A CoroutineExceptionHandler was passed to TestCoroutineScope. " +
+                "Please pass it as an argument to a `launch` or `async` block on an already-created scope " +
+                "if uncaught exceptions require special treatment."
+        )
+    }
+    val job: Job = ctxWithDispatcher[Job] ?: Job()
+    return TestCoroutineScopeImpl(ctxWithDispatcher + exceptionHandler + job).also {
+        scope = it
+    }
+}
+
+/** A marker that shows that this [CoroutineExceptionHandler] was created for [TestCoroutineScope]. With this,
+ * constructing a new [TestCoroutineScope] with the [CoroutineScope.coroutineContext] of an existing one will override
+ * the exception handler, instead of failing. */
+private interface TestCoroutineScopeExceptionHandler : CoroutineExceptionHandler
+
+private inline val CoroutineContext.delayController: DelayController?
+    get() {
+        val handler = this[ContinuationInterceptor]
+        return handler as? DelayController
+    }
+
+
+/**
+ * The current virtual time on [testScheduler][TestCoroutineScope.testScheduler].
+ * @see TestCoroutineScheduler.currentTime
+ */
+@ExperimentalCoroutinesApi
+public val TestCoroutineScope.currentTime: Long
+    get() = coroutineContext.delayController?.currentTime ?: testScheduler.currentTime
+
+/**
+ * Advances the [testScheduler][TestCoroutineScope.testScheduler] by [delayTimeMillis] and runs the tasks up to that
+ * moment (inclusive).
+ *
+ * @see TestCoroutineScheduler.advanceTimeBy
+ */
+@ExperimentalCoroutinesApi
+@Deprecated(
+    "The name of this function is misleading: it not only advances the time, but also runs the tasks " +
+        "scheduled *at* the ending moment.",
+    ReplaceWith("this.testScheduler.apply { advanceTimeBy(delayTimeMillis); runCurrent() }"),
+    DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun TestCoroutineScope.advanceTimeBy(delayTimeMillis: Long): Unit =
+    when (val controller = coroutineContext.delayController) {
+        null -> {
+            testScheduler.advanceTimeBy(delayTimeMillis)
+            testScheduler.runCurrent()
+        }
+        else -> {
+            controller.advanceTimeBy(delayTimeMillis)
+            Unit
+        }
+    }
+
+/**
+ * Advances the [testScheduler][TestCoroutineScope.testScheduler] to the point where there are no tasks remaining.
+ * @see TestCoroutineScheduler.advanceUntilIdle
+ */
+@ExperimentalCoroutinesApi
+public fun TestCoroutineScope.advanceUntilIdle() {
+    coroutineContext.delayController?.advanceUntilIdle() ?: testScheduler.advanceUntilIdle()
+}
+
+/**
+ * Run any tasks that are pending at the current virtual time, according to
+ * the [testScheduler][TestCoroutineScope.testScheduler].
+ *
+ * @see TestCoroutineScheduler.runCurrent
+ */
+@ExperimentalCoroutinesApi
+public fun TestCoroutineScope.runCurrent() {
+    coroutineContext.delayController?.runCurrent() ?: testScheduler.runCurrent()
+}
+
+@ExperimentalCoroutinesApi
+@Deprecated(
+    "The test coroutine scope isn't able to pause its dispatchers in the general case. " +
+        "Only `TestCoroutineDispatcher` supports pausing; pause it directly, or use a dispatcher that is always " +
+        "\"paused\", like `StandardTestDispatcher`.",
+    ReplaceWith(
+        "(this.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher(block)",
+        "kotlin.coroutines.ContinuationInterceptor"
+    ),
+    DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public suspend fun TestCoroutineScope.pauseDispatcher(block: suspend () -> Unit) {
+    delayControllerForPausing.pauseDispatcher(block)
+}
+
+@ExperimentalCoroutinesApi
+@Deprecated(
+    "The test coroutine scope isn't able to pause its dispatchers in the general case. " +
+        "Only `TestCoroutineDispatcher` supports pausing; pause it directly, or use a dispatcher that is always " +
+        "\"paused\", like `StandardTestDispatcher`.",
+    ReplaceWith(
+        "(this.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher()",
+        "kotlin.coroutines.ContinuationInterceptor"
+    ),
+    level = DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun TestCoroutineScope.pauseDispatcher() {
+    delayControllerForPausing.pauseDispatcher()
+}
+
+@ExperimentalCoroutinesApi
+@Deprecated(
+    "The test coroutine scope isn't able to pause its dispatchers in the general case. " +
+        "Only `TestCoroutineDispatcher` supports pausing; pause it directly, or use a dispatcher that is always " +
+        "\"paused\", like `StandardTestDispatcher`.",
+    ReplaceWith(
+        "(this.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher()",
+        "kotlin.coroutines.ContinuationInterceptor"
+    ),
+    level = DeprecationLevel.WARNING
+)
+// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
+public fun TestCoroutineScope.resumeDispatcher() {
+    delayControllerForPausing.resumeDispatcher()
+}
+
+/**
+ * List of uncaught coroutine exceptions, for backward compatibility.
+ *
+ * The returned list is a copy of the exceptions caught during execution.
+ * During [TestCoroutineScope.cleanupTestCoroutines] the first element of this list is rethrown if it is not empty.
+ *
+ * Exceptions are only collected in this list if the [UncaughtExceptionCaptor] is in the test context.
+ */
+@Deprecated(
+    "This list is only populated if `UncaughtExceptionCaptor` is in the test context, and so can be " +
+        "easily misused. It is only present for backward compatibility and will be removed in the subsequent " +
+        "releases. If you need to check the list of exceptions, please consider creating your own " +
+        "`CoroutineExceptionHandler`.",
+    level = DeprecationLevel.WARNING
+)
+public val TestCoroutineScope.uncaughtExceptions: List<Throwable>
+    get() = (coroutineContext[CoroutineExceptionHandler] as? UncaughtExceptionCaptor)?.uncaughtExceptions
+        ?: emptyList()
+
+private val TestCoroutineScope.delayControllerForPausing: DelayController
+    get() = coroutineContext.delayController
+        ?: throw IllegalStateException("This scope isn't able to pause its dispatchers")
diff --git a/kotlinx-coroutines-test/jvm/test/HelpersJvm.kt b/kotlinx-coroutines-test/jvm/test/HelpersJvm.kt
new file mode 100644
index 0000000..e9aa3ff
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/test/HelpersJvm.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines.test
+
+actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult) {
+    block {
+        test()
+    }
+}
diff --git a/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt b/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt
new file mode 100644
index 0000000..90a16d0
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.*
+import kotlin.concurrent.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class MultithreadingTest {
+
+    @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
+    fun testSingleThreadExecutor() = runBlocking {
+        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 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())
+        }
+    }
+
+    /** Tests that resuming the coroutine of [runTest] asynchronously in reasonable time succeeds. */
+    @Test
+    fun testResumingFromAnotherThread() = runTest {
+        suspendCancellableCoroutine<Unit> { cont ->
+            thread {
+                Thread.sleep(10)
+                cont.resume(Unit)
+            }
+        }
+    }
+
+    /** Tests that [StandardTestDispatcher] is confined to the thread that interacts with the scheduler. */
+    @Test
+    fun testStandardTestDispatcherIsConfined() = runTest {
+        val initialThread = Thread.currentThread()
+        withContext(Dispatchers.IO) {
+            val ioThread = Thread.currentThread()
+            assertNotSame(initialThread, ioThread)
+        }
+        assertEquals(initialThread, Thread.currentThread())
+    }
+}
diff --git a/kotlinx-coroutines-test/jvm/test/RunTestStressTest.kt b/kotlinx-coroutines-test/jvm/test/RunTestStressTest.kt
new file mode 100644
index 0000000..3edaa48
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/test/RunTestStressTest.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016-2021 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.concurrent.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class RunTestStressTest {
+    /** Tests that notifications about asynchronous resumptions aren't lost. */
+    @Test
+    fun testRunTestActivityNotificationsRace() {
+        val n = 1_000 * stressTestMultiplier
+        for (i in 0 until n) {
+            runTest {
+                suspendCancellableCoroutine<Unit> { cont ->
+                    thread {
+                        cont.resume(Unit)
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/jvm/test/migration/RunBlockingTestOnTestScopeTest.kt b/kotlinx-coroutines-test/jvm/test/migration/RunBlockingTestOnTestScopeTest.kt
new file mode 100644
index 0000000..8065920
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/test/migration/RunBlockingTestOnTestScopeTest.kt
@@ -0,0 +1,373 @@
+/*
+ * Copyright 2016-2021 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.channels.*
+import kotlinx.coroutines.flow.*
+import kotlin.test.*
+
+/** Copy of [RunTestTest], but for [runBlockingTestOnTestScope], where applicable. */
+@Suppress("DEPRECATION")
+class RunBlockingTestOnTestScopeTest {
+
+    @Test
+    fun testRunTestWithIllegalContext() {
+        for (ctx in TestScopeTest.invalidContexts) {
+            assertFailsWith<IllegalArgumentException> {
+                runBlockingTestOnTestScope(ctx) { }
+            }
+        }
+    }
+
+    @Test
+    fun testThrowingInRunTestBody() {
+        assertFailsWith<RuntimeException> {
+            runBlockingTestOnTestScope {
+                throw RuntimeException()
+            }
+        }
+    }
+
+    @Test
+    fun testThrowingInRunTestPendingTask() {
+        assertFailsWith<RuntimeException> {
+            runBlockingTestOnTestScope {
+                launch {
+                    delay(SLOW)
+                    throw RuntimeException()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun reproducer2405() = runBlockingTestOnTestScope {
+        val dispatcher = StandardTestDispatcher(testScheduler)
+        var collectedError = false
+        withContext(dispatcher) {
+            flow { emit(1) }
+                .combine(
+                    flow<String> { throw IllegalArgumentException() }
+                ) { int, string -> int.toString() + string }
+                .catch { emit("error") }
+                .collect {
+                    assertEquals("error", it)
+                    collectedError = true
+                }
+        }
+        assertTrue(collectedError)
+    }
+
+    @Test
+    fun testChildrenCancellationOnTestBodyFailure() {
+        var job: Job? = null
+        assertFailsWith<AssertionError> {
+            runBlockingTestOnTestScope {
+                job = launch {
+                    while (true) {
+                        delay(1000)
+                    }
+                }
+                throw AssertionError()
+            }
+        }
+        assertTrue(job!!.isCancelled)
+    }
+
+    @Test
+    fun testTimeout() {
+        assertFailsWith<TimeoutCancellationException> {
+            runBlockingTestOnTestScope {
+                withTimeout(50) {
+                    launch {
+                        delay(1000)
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testRunTestThrowsRootCause() {
+        assertFailsWith<TestException> {
+            runBlockingTestOnTestScope {
+                launch {
+                    throw TestException()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testCompletesOwnJob() {
+        var handlerCalled = false
+        runBlockingTestOnTestScope {
+            coroutineContext.job.invokeOnCompletion {
+                handlerCalled = true
+            }
+        }
+        assertTrue(handlerCalled)
+    }
+
+    @Test
+    fun testDoesNotCompleteGivenJob() {
+        var handlerCalled = false
+        val job = Job()
+        job.invokeOnCompletion {
+            handlerCalled = true
+        }
+        runBlockingTestOnTestScope(job) {
+            assertTrue(coroutineContext.job in job.children)
+        }
+        assertFalse(handlerCalled)
+        assertEquals(0, job.children.filter { it.isActive }.count())
+    }
+
+    @Test
+    fun testSuppressedExceptions() {
+        try {
+            runBlockingTestOnTestScope {
+                launch(SupervisorJob()) { throw TestException("x") }
+                launch(SupervisorJob()) { throw TestException("y") }
+                launch(SupervisorJob()) { throw TestException("z") }
+                throw TestException("w")
+            }
+            fail("should not be reached")
+        } catch (e: TestException) {
+            assertEquals("w", e.message)
+            val suppressed = e.suppressedExceptions +
+                (e.suppressedExceptions.firstOrNull()?.suppressedExceptions ?: emptyList())
+            assertEquals(3, suppressed.size)
+            assertEquals("x", suppressed[0].message)
+            assertEquals("y", suppressed[1].message)
+            assertEquals("z", suppressed[2].message)
+        }
+    }
+
+    @Test
+    fun testScopeRunTestExceptionHandler(): TestResult {
+        val scope = TestCoroutineScope()
+        return testResultMap({
+            try {
+                it()
+                fail("should not be reached")
+            } catch (e: TestException) {
+                // expected
+            }
+        }) {
+            scope.runTest {
+                launch(SupervisorJob()) { throw TestException("x") }
+            }
+        }
+    }
+
+    @Test
+    fun testBackgroundWorkBeingRun() = runBlockingTestOnTestScope {
+        var i = 0
+        var j = 0
+        backgroundScope.launch {
+            yield()
+            ++i
+        }
+        backgroundScope.launch {
+            yield()
+            delay(10)
+            ++j
+        }
+        assertEquals(0, i)
+        assertEquals(0, j)
+        delay(1)
+        assertEquals(1, i)
+        assertEquals(0, j)
+        delay(10)
+        assertEquals(1, i)
+        assertEquals(1, j)
+    }
+
+    @Test
+    fun testBackgroundWorkCancelled() {
+        var cancelled = false
+        runBlockingTestOnTestScope {
+            var i = 0
+            backgroundScope.launch {
+                yield()
+                try {
+                    while (isActive) {
+                        ++i
+                        yield()
+                    }
+                } catch (e: CancellationException) {
+                    cancelled = true
+                }
+            }
+            repeat(5) {
+                assertEquals(i, it)
+                yield()
+            }
+        }
+        assertTrue(cancelled)
+    }
+
+    @Test
+    fun testBackgroundWorkTimeControl(): TestResult = runBlockingTestOnTestScope {
+        var i = 0
+        var j = 0
+        backgroundScope.launch {
+            yield()
+            while (true) {
+                ++i
+                delay(100)
+            }
+        }
+        backgroundScope.launch {
+            yield()
+            while (true) {
+                ++j
+                delay(50)
+            }
+        }
+        advanceUntilIdle() // should do nothing, as only background work is left.
+        assertEquals(0, i)
+        assertEquals(0, j)
+        val job = launch {
+            delay(1)
+            // the background work scheduled for earlier gets executed before the normal work scheduled for later does
+            assertEquals(1, i)
+            assertEquals(1, j)
+        }
+        job.join()
+        advanceTimeBy(199) // should work the same for the background tasks
+        assertEquals(2, i)
+        assertEquals(4, j)
+        advanceUntilIdle() // once again, should do nothing
+        assertEquals(2, i)
+        assertEquals(4, j)
+        runCurrent() // should behave the same way as for the normal work
+        assertEquals(3, i)
+        assertEquals(5, j)
+        launch {
+            delay(1001)
+            assertEquals(13, i)
+            assertEquals(25, j)
+        }
+        advanceUntilIdle() // should execute the normal work, and with that, the background one, too
+    }
+
+    @Test
+    fun testBackgroundWorkErrorReporting() {
+        var testFinished = false
+        val exception = RuntimeException("x")
+        try {
+            runBlockingTestOnTestScope {
+                backgroundScope.launch {
+                    throw exception
+                }
+                delay(1000)
+                testFinished = true
+            }
+            fail("unreached")
+        } catch (e: Throwable) {
+            assertSame(e, exception)
+            assertTrue(testFinished)
+        }
+    }
+
+    @Test
+    fun testBackgroundWorkFinalizing() {
+        var taskEnded = 0
+        val nTasks = 10
+        try {
+            runBlockingTestOnTestScope {
+                repeat(nTasks) {
+                    backgroundScope.launch {
+                        try {
+                            while (true) {
+                                delay(1)
+                            }
+                        } finally {
+                            ++taskEnded
+                            if (taskEnded <= 2)
+                                throw TestException()
+                        }
+                    }
+                }
+                delay(100)
+                throw TestException()
+            }
+            fail("unreached")
+        } catch (e: TestException) {
+            assertEquals(2, e.suppressedExceptions.size)
+            assertEquals(nTasks, taskEnded)
+        }
+    }
+
+    @Test
+    fun testExampleBackgroundJob1() = runBlockingTestOnTestScope {
+        val myFlow = flow {
+            yield()
+            var i = 0
+            while (true) {
+                emit(++i)
+                delay(1)
+            }
+        }
+        val stateFlow = myFlow.stateIn(backgroundScope, SharingStarted.Eagerly, 0)
+        var j = 0
+        repeat(100) {
+            assertEquals(j++, stateFlow.value)
+            delay(1)
+        }
+    }
+
+    @Test
+    fun testExampleBackgroundJob2() = runBlockingTestOnTestScope {
+        val channel = Channel<Int>()
+        backgroundScope.launch {
+            var i = 0
+            while (true) {
+                channel.send(i++)
+            }
+        }
+        repeat(100) {
+            assertEquals(it, channel.receive())
+        }
+    }
+
+    @Test
+    fun testAsyncFailureInBackgroundReported() =
+        try {
+            runBlockingTestOnTestScope {
+                backgroundScope.async {
+                    throw TestException("x")
+                }
+                backgroundScope.produce<Unit> {
+                    throw TestException("y")
+                }
+                delay(1)
+                throw TestException("z")
+            }
+            fail("unreached")
+        } catch (e: TestException) {
+            assertEquals("z", e.message)
+            assertEquals(setOf("x", "y"), e.suppressedExceptions.map { it.message }.toSet())
+        }
+
+    @Test
+    fun testNoDuplicateExceptions() =
+        try {
+            runBlockingTestOnTestScope {
+                backgroundScope.launch {
+                    throw TestException("x")
+                }
+                delay(1)
+                throw TestException("y")
+            }
+            fail("unreached")
+        } catch (e: TestException) {
+            assertEquals("y", e.message)
+            assertEquals(listOf("x"), e.suppressedExceptions.map { it.message })
+        }
+}
diff --git a/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt b/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt
new file mode 100644
index 0000000..7f1dd00
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2016-2021 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.flow.*
+import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+/** Copy of [RunTestTest], but for [TestCoroutineScope] */
+@Suppress("DEPRECATION")
+class RunTestLegacyScopeTest {
+
+    @Test
+    fun testWithContextDispatching() = runTestWithLegacyScope {
+        var counter = 0
+        withContext(Dispatchers.Default) {
+            counter += 1
+        }
+        assertEquals(counter, 1)
+    }
+
+    @Test
+    fun testJoiningForkedJob() = runTestWithLegacyScope {
+        var counter = 0
+        val job = GlobalScope.launch {
+            counter += 1
+        }
+        job.join()
+        assertEquals(counter, 1)
+    }
+
+    @Test
+    fun testSuspendCoroutine() = runTestWithLegacyScope {
+        val answer = suspendCoroutine<Int> {
+            it.resume(42)
+        }
+        assertEquals(42, answer)
+    }
+
+    @Test
+    fun testNestedRunTestForbidden() = runTestWithLegacyScope {
+        assertFailsWith<IllegalStateException> {
+            runTest { }
+        }
+    }
+
+    @Test
+    fun testRunTestWithZeroTimeoutWithControlledDispatches() = runTestWithLegacyScope(dispatchTimeoutMs = 0) {
+        // below is some arbitrary concurrent code where all dispatches go through the same scheduler.
+        launch {
+            delay(2000)
+        }
+        val deferred = async {
+            val job = launch(StandardTestDispatcher(testScheduler)) {
+                launch {
+                    delay(500)
+                }
+                delay(1000)
+            }
+            job.join()
+        }
+        deferred.await()
+    }
+
+    @Test
+    fun testRunTestWithZeroTimeoutWithUncontrolledDispatches() = testResultMap({ fn ->
+        assertFailsWith<UncompletedCoroutinesError> { fn() }
+    }) {
+        runTestWithLegacyScope(dispatchTimeoutMs = 0) {
+            withContext(Dispatchers.Default) {
+                delay(10)
+                3
+            }
+            fail("shouldn't be reached")
+        }
+    }
+
+    @Test
+    fun testRunTestWithSmallTimeout() = testResultMap({ fn ->
+        assertFailsWith<UncompletedCoroutinesError> { fn() }
+    }) {
+        runTestWithLegacyScope(dispatchTimeoutMs = 100) {
+            withContext(Dispatchers.Default) {
+                delay(10000)
+                3
+            }
+            fail("shouldn't be reached")
+        }
+    }
+
+    @Test
+    fun testRunTestWithLargeTimeout() = runTestWithLegacyScope(dispatchTimeoutMs = 5000) {
+        withContext(Dispatchers.Default) {
+            delay(50)
+        }
+    }
+
+    @Test
+    fun testRunTestTimingOutAndThrowing() = testResultMap({ fn ->
+        try {
+            fn()
+            fail("unreached")
+        } catch (e: UncompletedCoroutinesError) {
+            @Suppress("INVISIBLE_MEMBER")
+            val suppressed = unwrap(e).suppressedExceptions
+            assertEquals(1, suppressed.size)
+            assertIs<TestException>(suppressed[0]).also {
+                assertEquals("A", it.message)
+            }
+        }
+    }) {
+        runTestWithLegacyScope(dispatchTimeoutMs = 1) {
+            coroutineContext[CoroutineExceptionHandler]!!.handleException(coroutineContext, TestException("A"))
+            withContext(Dispatchers.Default) {
+                delay(10000)
+                3
+            }
+            fail("shouldn't be reached")
+        }
+    }
+
+    @Test
+    fun testRunTestWithIllegalContext() {
+        for (ctx in TestScopeTest.invalidContexts) {
+            assertFailsWith<IllegalArgumentException> {
+                runTestWithLegacyScope(ctx) { }
+            }
+        }
+    }
+
+    @Test
+    fun testThrowingInRunTestBody() = testResultMap({
+        assertFailsWith<RuntimeException> { it() }
+    }) {
+        runTestWithLegacyScope {
+            throw RuntimeException()
+        }
+    }
+
+    @Test
+    fun testThrowingInRunTestPendingTask() = testResultMap({
+        assertFailsWith<RuntimeException> { it() }
+    }) {
+        runTestWithLegacyScope {
+            launch {
+                delay(SLOW)
+                throw RuntimeException()
+            }
+        }
+    }
+
+    @Test
+    fun reproducer2405() = runTestWithLegacyScope {
+        val dispatcher = StandardTestDispatcher(testScheduler)
+        var collectedError = false
+        withContext(dispatcher) {
+            flow { emit(1) }
+                .combine(
+                    flow<String> { throw IllegalArgumentException() }
+                ) { int, string -> int.toString() + string }
+                .catch { emit("error") }
+                .collect {
+                    assertEquals("error", it)
+                    collectedError = true
+                }
+        }
+        assertTrue(collectedError)
+    }
+
+    @Test
+    fun testChildrenCancellationOnTestBodyFailure(): TestResult {
+        var job: Job? = null
+        return testResultMap({
+            assertFailsWith<AssertionError> { it() }
+            assertTrue(job!!.isCancelled)
+        }) {
+            runTestWithLegacyScope {
+                job = launch {
+                    while (true) {
+                        delay(1000)
+                    }
+                }
+                throw AssertionError()
+            }
+        }
+    }
+
+    @Test
+    fun testTimeout() = testResultMap({
+        assertFailsWith<TimeoutCancellationException> { it() }
+    }) {
+        runTestWithLegacyScope {
+            withTimeout(50) {
+                launch {
+                    delay(1000)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testRunTestThrowsRootCause() = testResultMap({
+        assertFailsWith<TestException> { it() }
+    }) {
+        runTestWithLegacyScope {
+            launch {
+                throw TestException()
+            }
+        }
+    }
+
+    @Test
+    fun testCompletesOwnJob(): TestResult {
+        var handlerCalled = false
+        return testResultMap({
+            it()
+            assertTrue(handlerCalled)
+        }) {
+            runTestWithLegacyScope {
+                coroutineContext.job.invokeOnCompletion {
+                    handlerCalled = true
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testDoesNotCompleteGivenJob(): TestResult {
+        var handlerCalled = false
+        val job = Job()
+        job.invokeOnCompletion {
+            handlerCalled = true
+        }
+        return testResultMap({
+            it()
+            assertFalse(handlerCalled)
+            assertEquals(0, job.children.filter { it.isActive }.count())
+        }) {
+            runTestWithLegacyScope(job) {
+                assertTrue(coroutineContext.job in job.children)
+            }
+        }
+    }
+
+    @Test
+    fun testSuppressedExceptions() = testResultMap({
+        try {
+            it()
+            fail("should not be reached")
+        } catch (e: TestException) {
+            assertEquals("w", e.message)
+            val suppressed = e.suppressedExceptions +
+                (e.suppressedExceptions.firstOrNull()?.suppressedExceptions ?: emptyList())
+            assertEquals(3, suppressed.size)
+            assertEquals("x", suppressed[0].message)
+            assertEquals("y", suppressed[1].message)
+            assertEquals("z", suppressed[2].message)
+        }
+    }) {
+        runTestWithLegacyScope {
+            launch(SupervisorJob()) { throw TestException("x") }
+            launch(SupervisorJob()) { throw TestException("y") }
+            launch(SupervisorJob()) { throw TestException("z") }
+            throw TestException("w")
+        }
+    }
+
+    @Test
+    fun testScopeRunTestExceptionHandler(): TestResult {
+        val scope = TestCoroutineScope()
+        return testResultMap({
+            try {
+                it()
+                fail("should not be reached")
+            } catch (e: TestException) {
+                // expected
+            }
+        }) {
+            scope.runTest {
+                launch(SupervisorJob()) { throw TestException("x") }
+            }
+        }
+    }
+}
diff --git a/kotlinx-coroutines-test/test/TestBuildersTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestBuildersTest.kt
similarity index 95%
rename from kotlinx-coroutines-test/test/TestBuildersTest.kt
rename to kotlinx-coroutines-test/jvm/test/migration/TestBuildersTest.kt
index 27c8f5f..6d49a01 100644
--- a/kotlinx-coroutines-test/test/TestBuildersTest.kt
+++ b/kotlinx-coroutines-test/jvm/test/migration/TestBuildersTest.kt
@@ -5,10 +5,10 @@
 package kotlinx.coroutines.test
 
 import kotlinx.coroutines.*
-import org.junit.Test
 import kotlin.coroutines.*
 import kotlin.test.*
 
+@Suppress("DEPRECATION")
 class TestBuildersTest {
 
     @Test
@@ -59,7 +59,7 @@
     }
 
     @Test
-    fun scopeRunBlocking_disablesImmedateOnExit() {
+    fun scopeRunBlocking_disablesImmediatelyOnExit() {
         val scope = TestCoroutineScope()
         scope.runBlockingTest {
             assertRunsFast {
@@ -105,7 +105,7 @@
     }
 
     @Test
-    fun whenInrunBlocking_runBlockingTest_nestsProperly() {
+    fun whenInRunBlocking_runBlockingTest_nestsProperly() {
         // this is not a supported use case, but it is possible so ensure it works
 
         val scope = TestCoroutineScope()
diff --git a/kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherOrderTest.kt
similarity index 78%
rename from kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt
rename to kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherOrderTest.kt
index 116aadc..93fcd90 100644
--- a/kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt
+++ b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherOrderTest.kt
@@ -1,11 +1,15 @@
+/*
+ * Copyright 2016-2021 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 org.junit.*
-import kotlin.coroutines.*
-import kotlin.test.assertEquals
+import kotlin.test.*
 
-class TestCoroutineDispatcherOrderTest : TestBase() {
+@Suppress("DEPRECATION")
+class TestCoroutineDispatcherOrderTest: OrderedExecutionTestBase() {
 
     @Test
     fun testAdvanceTimeBy_progressesOnEachDelay() {
diff --git a/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherTest.kt
new file mode 100644
index 0000000..a78d923
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-2021 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.test.*
+
+@Suppress("DEPRECATION")
+class TestCoroutineDispatcherTest {
+    @Test
+    fun whenDispatcherPaused_doesNotAutoProgressCurrent() {
+        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
+    fun whenDispatcherHasUncompletedCoroutines_itThrowsErrorInCleanup() {
+        val subject = TestCoroutineDispatcher()
+        subject.pauseDispatcher()
+        val scope = CoroutineScope(subject)
+        scope.launch {
+            delay(1_000)
+        }
+        assertFailsWith<UncompletedCoroutinesError> { subject.cleanupTestCoroutines() }
+    }
+
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineExceptionHandlerTest.kt
similarity index 72%
rename from kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt
rename to kotlinx-coroutines-test/jvm/test/migration/TestCoroutineExceptionHandlerTest.kt
index 1a0833a..20da130 100644
--- a/kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt
+++ b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineExceptionHandlerTest.kt
@@ -1,15 +1,15 @@
 /*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.test
 
-import org.junit.Test
 import kotlin.test.*
 
+@Suppress("DEPRECATION")
 class TestCoroutineExceptionHandlerTest {
     @Test
-    fun whenExceptionsCaught_avaliableViaProperty() {
+    fun whenExceptionsCaught_availableViaProperty() {
         val subject = TestCoroutineExceptionHandler()
         val expected = IllegalArgumentException()
         subject.handleException(subject, expected)
diff --git a/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineScopeTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineScopeTest.kt
new file mode 100644
index 0000000..1a62613
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineScopeTest.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("DEPRECATION")
+
+package kotlinx.coroutines.test
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class TestCoroutineScopeTest {
+    /** Tests failing to create a [TestCoroutineScope] with incorrect contexts. */
+    @Test
+    fun testCreateThrowsOnInvalidArguments() {
+        for (ctx in invalidContexts) {
+            assertFailsWith<IllegalArgumentException> {
+                createTestCoroutineScope(ctx)
+            }
+        }
+    }
+
+    /** Tests that a newly-created [TestCoroutineScope] provides the correct scheduler. */
+    @Test
+    fun testCreateProvidesScheduler() {
+        // Creates a new scheduler.
+        run {
+            val scope = createTestCoroutineScope()
+            assertNotNull(scope.coroutineContext[TestCoroutineScheduler])
+        }
+        // Reuses the scheduler that the dispatcher is linked to.
+        run {
+            val dispatcher = StandardTestDispatcher()
+            val scope = createTestCoroutineScope(dispatcher)
+            assertSame(dispatcher.scheduler, scope.coroutineContext[TestCoroutineScheduler])
+        }
+        // Uses the scheduler passed to it.
+        run {
+            val scheduler = TestCoroutineScheduler()
+            val scope = createTestCoroutineScope(scheduler)
+            assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
+            assertSame(scheduler, (scope.coroutineContext[ContinuationInterceptor] as TestDispatcher).scheduler)
+        }
+        // Doesn't touch the passed dispatcher and the scheduler if they match.
+        run {
+            val scheduler = TestCoroutineScheduler()
+            val dispatcher = StandardTestDispatcher(scheduler)
+            val scope = createTestCoroutineScope(scheduler + dispatcher)
+            assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
+            assertSame(dispatcher, scope.coroutineContext[ContinuationInterceptor])
+        }
+        // Reuses the scheduler of `Dispatchers.Main`
+        run {
+            val scheduler = TestCoroutineScheduler()
+            val mainDispatcher = StandardTestDispatcher(scheduler)
+            Dispatchers.setMain(mainDispatcher)
+            try {
+                val scope = createTestCoroutineScope()
+                assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
+                assertNotSame(mainDispatcher, scope.coroutineContext[ContinuationInterceptor])
+            } finally {
+                Dispatchers.resetMain()
+            }
+        }
+        // Does not reuse the scheduler of `Dispatchers.Main` if one is explicitly passed
+        run {
+            val mainDispatcher = StandardTestDispatcher()
+            Dispatchers.setMain(mainDispatcher)
+            try {
+                val scheduler = TestCoroutineScheduler()
+                val scope = createTestCoroutineScope(scheduler)
+                assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
+                assertNotSame(mainDispatcher.scheduler, scope.coroutineContext[TestCoroutineScheduler])
+                assertNotSame(mainDispatcher, scope.coroutineContext[ContinuationInterceptor])
+            } finally {
+                Dispatchers.resetMain()
+            }
+        }
+    }
+
+    /** Tests that the cleanup procedure throws if there were uncompleted delays by the end. */
+    @Test
+    fun testPresentDelaysThrowing() {
+        val scope = createTestCoroutineScope()
+        var result = false
+        scope.launch {
+            delay(5)
+            result = true
+        }
+        assertFalse(result)
+        assertFailsWith<AssertionError> { scope.cleanupTestCoroutines() }
+        assertFalse(result)
+    }
+
+    /** Tests that the cleanup procedure throws if there were active jobs by the end. */
+    @Test
+    fun testActiveJobsThrowing() {
+        val scope = createTestCoroutineScope()
+        var result = false
+        val deferred = CompletableDeferred<String>()
+        scope.launch {
+            deferred.await()
+            result = true
+        }
+        assertFalse(result)
+        assertFailsWith<AssertionError> { scope.cleanupTestCoroutines() }
+        assertFalse(result)
+    }
+
+    /** Tests that the cleanup procedure doesn't throw if it detects that the job is already cancelled. */
+    @Test
+    fun testCancelledDelaysNotThrowing() {
+        val scope = createTestCoroutineScope()
+        var result = false
+        val deferred = CompletableDeferred<String>()
+        val job = scope.launch {
+            deferred.await()
+            result = true
+        }
+        job.cancel()
+        assertFalse(result)
+        scope.cleanupTestCoroutines()
+        assertFalse(result)
+    }
+
+    /** Tests that uncaught exceptions are thrown at the cleanup. */
+    @Test
+    fun testThrowsUncaughtExceptionsOnCleanup() {
+        val scope = createTestCoroutineScope()
+        val exception = TestException("test")
+        scope.launch {
+            throw exception
+        }
+        assertFailsWith<TestException> {
+            scope.cleanupTestCoroutines()
+        }
+    }
+
+    /** Tests that uncaught exceptions take priority over uncompleted jobs when throwing on cleanup. */
+    @Test
+    fun testUncaughtExceptionsPrioritizedOnCleanup() {
+        val scope = createTestCoroutineScope()
+        val exception = TestException("test")
+        scope.launch {
+            throw exception
+        }
+        scope.launch {
+            delay(1000)
+        }
+        assertFailsWith<TestException> {
+            scope.cleanupTestCoroutines()
+        }
+    }
+
+    /** Tests that cleaning up twice is forbidden. */
+    @Test
+    fun testClosingTwice() {
+        val scope = createTestCoroutineScope()
+        scope.cleanupTestCoroutines()
+        assertFailsWith<IllegalStateException> {
+            scope.cleanupTestCoroutines()
+        }
+    }
+
+    /** Tests that, when reporting several exceptions, the first one is thrown, with the rest suppressed. */
+    @Test
+    fun testSuppressedExceptions() {
+        createTestCoroutineScope().apply {
+            launch(SupervisorJob()) { throw TestException("x") }
+            launch(SupervisorJob()) { throw TestException("y") }
+            launch(SupervisorJob()) { throw TestException("z") }
+            try {
+                cleanupTestCoroutines()
+                fail("should not be reached")
+            } catch (e: TestException) {
+                assertEquals("x", e.message)
+                assertEquals(2, e.suppressedExceptions.size)
+                assertEquals("y", e.suppressedExceptions[0].message)
+                assertEquals("z", e.suppressedExceptions[1].message)
+            }
+        }
+    }
+
+    /** Tests that constructing a new [TestCoroutineScope] using another one's scope works and overrides the exception
+     * handler. */
+    @Test
+    fun testCopyingContexts() {
+        val deferred = CompletableDeferred<Unit>()
+        val scope1 = createTestCoroutineScope()
+        scope1.launch { deferred.await() } // a pending job in the outer scope
+        val scope2 = createTestCoroutineScope(scope1.coroutineContext)
+        val scope3 = createTestCoroutineScope(scope1.coroutineContext)
+        assertEquals(
+            scope1.coroutineContext.minusKey(CoroutineExceptionHandler),
+            scope2.coroutineContext.minusKey(CoroutineExceptionHandler))
+        scope2.launch(SupervisorJob()) { throw TestException("x") } // will fail the cleanup of scope2
+        try {
+            scope2.cleanupTestCoroutines()
+            fail("should not be reached")
+        } catch (e: TestException) { }
+        scope3.cleanupTestCoroutines() // the pending job in the outer scope will not cause this to fail
+        try {
+            scope1.cleanupTestCoroutines()
+            fail("should not be reached")
+        } catch (e: UncompletedCoroutinesError) {
+            // the pending job in the outer scope
+        }
+    }
+
+    companion object {
+        internal val invalidContexts = listOf(
+            Dispatchers.Default, // not a [TestDispatcher]
+            CoroutineExceptionHandler { _, _ -> }, // not an [UncaughtExceptionCaptor]
+            StandardTestDispatcher() + TestCoroutineScheduler(), // the dispatcher is not linked to the scheduler
+        )
+    }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingOrderTest.kt
similarity index 89%
rename from kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt
rename to kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingOrderTest.kt
index e21c82b..32514d9 100644
--- a/kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt
+++ b/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingOrderTest.kt
@@ -1,14 +1,15 @@
 /*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.test
 
 import kotlinx.coroutines.*
-import org.junit.*
-import kotlin.coroutines.*
+import kotlin.test.*
 
-class TestRunBlockingOrderTest : TestBase() {
+@Suppress("DEPRECATION")
+class TestRunBlockingOrderTest: OrderedExecutionTestBase() {
+
     @Test
     fun testLaunchImmediate() = runBlockingTest {
         expect(1)
@@ -76,4 +77,4 @@
         }
         finish(2)
     }
-}
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingTest.kt
new file mode 100644
index 0000000..af3b248
--- /dev/null
+++ b/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingTest.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.test
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+@Suppress("DEPRECATION")
+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 whenUsingTimeout_triggersWhenDelayed() {
+        assertFailsWith<TimeoutCancellationException> {
+            runBlockingTest {
+                assertRunsFast {
+                    withTimeout(SLOW) {
+                        delay(SLOW)
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun whenUsingTimeout_doesNotTriggerWhenFast() = runBlockingTest {
+        assertRunsFast {
+            withTimeout(SLOW) {
+                delay(0)
+            }
+        }
+    }
+
+    @Test
+    fun whenUsingTimeout_triggersWhenWaiting() {
+        assertFailsWith<TimeoutCancellationException> {
+            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
+    fun whenUsingTimeout_inAsync_triggersWhenDelayed() {
+        assertFailsWith<TimeoutCancellationException> {
+            runBlockingTest {
+                val deferred = async {
+                    withTimeout(SLOW) {
+                        delay(SLOW)
+                    }
+                }
+
+                assertRunsFast {
+                    deferred.await()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun whenUsingTimeout_inAsync_doesNotTriggerWhenNotDelayed() = runBlockingTest {
+        val deferred = async {
+            withTimeout(SLOW) {
+                delay(0)
+            }
+        }
+
+        assertRunsFast {
+            deferred.await()
+        }
+    }
+
+    @Test
+    fun whenUsingTimeout_inLaunch_triggersWhenDelayed() {
+        assertFailsWith<TimeoutCancellationException> {
+            runBlockingTest {
+                val job = launch {
+                    withTimeout(1) {
+                        delay(SLOW + 1)
+                    }
+                }
+
+                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
+    fun throwingException_throws() {
+        assertFailsWith<IllegalArgumentException> {
+            runBlockingTest {
+                assertRunsFast {
+                    delay(SLOW)
+                    throw IllegalArgumentException("Test")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun throwingException_inLaunch_throws() {
+        assertFailsWith<IllegalArgumentException> {
+            runBlockingTest {
+                val job = launch {
+                    delay(SLOW)
+                    throw IllegalArgumentException("Test")
+                }
+
+                assertRunsFast {
+                    job.join()
+                    throw job.getCancellationException().cause ?: AssertionError("expected exception")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun throwingException__inAsync_throws() {
+        assertFailsWith<IllegalArgumentException> {
+            runBlockingTest {
+                val deferred: Deferred<Unit> = 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
+            val deferred = async {
+                delay(SLOW)
+                executed = true
+            }
+            advanceTimeBy(SLOW)
+
+            assertTrue(deferred.isCompleted)
+            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
+    fun whenACoroutineLeaks_errorIsThrown() {
+        assertFailsWith<UncompletedCoroutinesError> {
+            runBlockingTest {
+                val uncompleted = CompletableDeferred<Unit>()
+                launch {
+                    uncompleted.await()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun runBlockingTestBuilder_throwsOnBadDispatcher() {
+        assertFailsWith<IllegalArgumentException> {
+            runBlockingTest(Dispatchers.Default) {
+
+            }
+        }
+    }
+
+    @Test
+    fun runBlockingTestBuilder_throwsOnBadHandler() {
+        assertFailsWith<IllegalArgumentException> {
+            runBlockingTest(CoroutineExceptionHandler { _, _ -> }) {
+
+            }
+        }
+    }
+
+    @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
+    fun testWithTestContextThrowingAnAssertionError() {
+        assertFailsWith<TestException> {
+            runBlockingTest {
+                val expectedError = TestException("hello")
+
+                launch {
+                    throw expectedError
+                }
+
+                // don't rethrow or handle the exception
+            }
+        }
+    }
+
+    @Test
+    fun testExceptionHandlingWithLaunch() {
+        assertFailsWith<TestException> {
+            runBlockingTest {
+                val expectedError = TestException("hello")
+
+                launch {
+                    throw expectedError
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testExceptions_notThrownImmediately() {
+        assertFailsWith<TestException> {
+            runBlockingTest {
+                val expectedException = TestException("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
+    fun testPartialDispatcherOverride() {
+        assertFailsWith<IllegalArgumentException> {
+            runBlockingTest(Dispatchers.Unconfined) {
+                fail("Unreached")
+            }
+        }
+    }
+
+    @Test
+    fun testOverrideExceptionHandler() = runBlockingTest(exceptionHandler) {
+        assertSame(coroutineContext[CoroutineExceptionHandler], exceptionHandler)
+    }
+
+    @Test
+    fun testOverrideExceptionHandlerError() {
+        assertFailsWith<IllegalArgumentException> {
+            runBlockingTest(CoroutineExceptionHandler { _, _ -> }) {
+                fail("Unreached")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/native/src/TestBuilders.kt b/kotlinx-coroutines-test/native/src/TestBuilders.kt
new file mode 100644
index 0000000..a959901
--- /dev/null
+++ b/kotlinx-coroutines-test/native/src/TestBuilders.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test
+import kotlinx.coroutines.*
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual typealias TestResult = Unit
+
+internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> Unit) {
+    runBlocking {
+        testProcedure()
+    }
+}
diff --git a/kotlinx-coroutines-test/native/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/native/src/internal/TestMainDispatcher.kt
new file mode 100644
index 0000000..4d865f8
--- /dev/null
+++ b/kotlinx-coroutines-test/native/src/internal/TestMainDispatcher.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.test.internal
+import kotlinx.coroutines.*
+
+@Suppress("INVISIBLE_MEMBER")
+internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher =
+    when (val mainDispatcher = Main) {
+        is TestMainDispatcher -> mainDispatcher
+        else -> TestMainDispatcher(mainDispatcher).also { injectMain(it) }
+    }
diff --git a/kotlinx-coroutines-test/native/test/FailingTests.kt b/kotlinx-coroutines-test/native/test/FailingTests.kt
new file mode 100644
index 0000000..9fb77ce
--- /dev/null
+++ b/kotlinx-coroutines-test/native/test/FailingTests.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2021 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.test.*
+
+/** These are tests that we want to fail. They are here so that, when the issue is fixed, their failure indicates that
+ * everything is better now. */
+class FailingTests {
+    @Test
+    fun testRunTestLoopShutdownOnTimeout() = testResultMap({ fn ->
+        assertFailsWith<IllegalStateException> { fn() }
+    }) {
+        runTest(dispatchTimeoutMs = 1) {
+            withContext(Dispatchers.Default) {
+                delay(10000)
+            }
+            fail("shouldn't be reached")
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-test/native/test/Helpers.kt b/kotlinx-coroutines-test/native/test/Helpers.kt
new file mode 100644
index 0000000..ef478b7
--- /dev/null
+++ b/kotlinx-coroutines-test/native/test/Helpers.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines.test
+
+import kotlin.test.*
+
+actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult) {
+    block {
+        test()
+    }
+}
+
+actual typealias NoNative = Ignore
diff --git a/kotlinx-coroutines-test/npm/README.md b/kotlinx-coroutines-test/npm/README.md
new file mode 100644
index 0000000..4df4825
--- /dev/null
+++ b/kotlinx-coroutines-test/npm/README.md
@@ -0,0 +1,4 @@
+# kotlinx-coroutines-test
+
+Testing support for `kotlinx-coroutines` in
+[Kotlin/JS](https://kotlinlang.org/docs/js-overview.html).
diff --git a/kotlinx-coroutines-test/npm/package.json b/kotlinx-coroutines-test/npm/package.json
new file mode 100644
index 0000000..b59d92f
--- /dev/null
+++ b/kotlinx-coroutines-test/npm/package.json
@@ -0,0 +1,23 @@
+{
+  "name": "kotlinx-coroutines-test",
+  "version" : "$version",
+  "description" : "Test utilities for kotlinx-coroutines",
+  "main" : "kotlinx-coroutines-test.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",
+    "JetBrains",
+    "test"
+  ]
+}
diff --git a/kotlinx-coroutines-test/src/DelayController.kt b/kotlinx-coroutines-test/src/DelayController.kt
deleted file mode 100644
index 6e72222..0000000
--- a/kotlinx-coroutines-test/src/DelayController.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-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
deleted file mode 100644
index b40769e..0000000
--- a/kotlinx-coroutines-test/src/TestBuilders.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2016-2021 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): 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): 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
deleted file mode 100644
index f646478..0000000
--- a/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright 2016-2021 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, context: CoroutineContext): 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: Long 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(): Unit  = 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)"
-}
diff --git a/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt
deleted file mode 100644
index 66eb235..0000000
--- a/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2016-2021 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: List<Throwable>
-        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
deleted file mode 100644
index 7c1ff87..0000000
--- a/kotlinx-coroutines-test/src/TestCoroutineScope.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2016-2021 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)"
-        )
-    }
diff --git a/kotlinx-coroutines-test/src/TestDispatchers.kt b/kotlinx-coroutines-test/src/TestDispatchers.kt
deleted file mode 100644
index bf068f9..0000000
--- a/kotlinx-coroutines-test/src/TestDispatchers.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-@file:Suppress("unused")
-@file:JvmName("TestDispatchers")
-
-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
deleted file mode 100644
index c85d27e..0000000
--- a/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.test.internal
-
-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)
-    }
-
-    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, context: CoroutineContext): DisposableHandle {
-        return delay.invokeOnTimeout(timeMillis, block, context)
-    }
-
-    fun setDispatcher(dispatcher: CoroutineDispatcher) {
-        _delegate = dispatcher
-    }
-
-    fun resetDispatcher() {
-        _delegate = null
-    }
-}
-
-internal class TestMainDispatcherFactory : MainDispatcherFactory {
-
-    override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
-        val originalFactory = allFactories.asSequence()
-            .filter { it !== this }
-            .maxByOrNull { 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/TestCoroutineDispatcherTest.kt b/kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt
deleted file mode 100644
index 260edf9..0000000
--- a/kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.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/TestCoroutineScopeTest.kt b/kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt
deleted file mode 100644
index fa14c38..0000000
--- a/kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.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
deleted file mode 100644
index 98d9705..0000000
--- a/kotlinx-coroutines-test/test/TestDispatchersTest.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 12541bd..0000000
--- a/kotlinx-coroutines-test/test/TestModuleHelpers.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.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/TestRunBlockingTest.kt b/kotlinx-coroutines-test/test/TestRunBlockingTest.kt
deleted file mode 100644
index e0c7091..0000000
--- a/kotlinx-coroutines-test/test/TestRunBlockingTest.kt
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.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
index d1d00c1..8d1100a 100644
--- a/license/NOTICE.txt
+++ b/license/NOTICE.txt
@@ -5,4 +5,4 @@
 =========================================================================
 
 kotlinx.coroutines library.
-Copyright 2016-2019 JetBrains s.r.o and respective authors and developers
\ No newline at end of file
+Copyright 2016-2021 JetBrains s.r.o and respective authors and developers
diff --git a/reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt b/reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt
index 3682d5e..0479028 100644
--- a/reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt
+++ b/reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt
@@ -278,10 +278,8 @@
         val publisher = flowPublish {
             assertFailsWith<NullPointerException> { send(null) }
             assertFailsWith<NullPointerException> { trySend(null) }
-            @Suppress("DEPRECATION")
-            assertFailsWith<NullPointerException> { offer(null) }
             send("OK")
         }
         assertEquals("OK", publisher.awaitFirstOrNull())
     }
-}
\ No newline at end of file
+}
diff --git a/reactive/kotlinx-coroutines-reactive/README.md b/reactive/kotlinx-coroutines-reactive/README.md
index ec59d3d..cd27b27 100644
--- a/reactive/kotlinx-coroutines-reactive/README.md
+++ b/reactive/kotlinx-coroutines-reactive/README.md
@@ -33,23 +33,23 @@
 <!--- INDEX kotlinx.coroutines -->
 <!--- INDEX kotlinx.coroutines.flow -->
 
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+[Flow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
 
 <!--- INDEX kotlinx.coroutines.channels -->
 
-[ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
+[ProducerScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
 
 <!--- MODULE kotlinx-coroutines-reactive -->
 <!--- INDEX kotlinx.coroutines.reactive -->
 
-[kotlinx.coroutines.reactive.publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
-[Publisher.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/as-flow.html
-[Flow.asPublisher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/as-publisher.html
-[org.reactivestreams.Publisher.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first.html
-[org.reactivestreams.Publisher.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-default.html
-[org.reactivestreams.Publisher.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-else.html
-[org.reactivestreams.Publisher.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-null.html
-[org.reactivestreams.Publisher.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-single.html
+[kotlinx.coroutines.reactive.publish]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
+[Publisher.asFlow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/as-flow.html
+[Flow.asPublisher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/as-publisher.html
+[org.reactivestreams.Publisher.awaitFirst]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first.html
+[org.reactivestreams.Publisher.awaitFirstOrDefault]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-default.html
+[org.reactivestreams.Publisher.awaitFirstOrElse]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-else.html
+[org.reactivestreams.Publisher.awaitFirstOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-null.html
+[org.reactivestreams.Publisher.awaitSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-single.html
 
 <!--- END -->
 
diff --git a/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api b/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api
index 75f1b30..b52df18 100644
--- a/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api
+++ b/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api
@@ -12,9 +12,10 @@
 
 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 openSubscription (Lorg/reactivestreams/Publisher;I)Lkotlinx/coroutines/channels/ReceiveChannel;
+	public static final synthetic fun openSubscription (Lorg/reactivestreams/Publisher;I)Lkotlinx/coroutines/channels/ReceiveChannel;
 	public static synthetic fun openSubscription$default (Lorg/reactivestreams/Publisher;IILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
 	public static final fun toChannel (Lorg/reactivestreams/Publisher;I)Lkotlinx/coroutines/channels/ReceiveChannel;
+	public static synthetic fun toChannel$default (Lorg/reactivestreams/Publisher;IILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
 }
 
 public abstract interface class kotlinx/coroutines/reactive/ContextInjector {
@@ -22,7 +23,7 @@
 }
 
 public final class kotlinx/coroutines/reactive/ConvertKt {
-	public static final fun asPublisher (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lorg/reactivestreams/Publisher;
+	public static final synthetic 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;
 }
 
diff --git a/reactive/kotlinx-coroutines-reactive/build.gradle.kts b/reactive/kotlinx-coroutines-reactive/build.gradle.kts
index 128d4d8..c2e4b5c 100644
--- a/reactive/kotlinx-coroutines-reactive/build.gradle.kts
+++ b/reactive/kotlinx-coroutines-reactive/build.gradle.kts
@@ -5,8 +5,8 @@
 val reactiveStreamsVersion = property("reactive_streams_version")
 
 dependencies {
-    compile("org.reactivestreams:reactive-streams:$reactiveStreamsVersion")
-    testCompile("org.reactivestreams:reactive-streams-tck:$reactiveStreamsVersion")
+    api("org.reactivestreams:reactive-streams:$reactiveStreamsVersion")
+    testImplementation("org.reactivestreams:reactive-streams-tck:$reactiveStreamsVersion")
 }
 
 val testNG by tasks.registering(Test::class) {
@@ -34,3 +34,17 @@
 externalDocumentationLink(
     url = "https://www.reactive-streams.org/reactive-streams-$reactiveStreamsVersion-javadoc/"
 )
+
+val commonKoverExcludes = listOf(
+    "kotlinx.coroutines.reactive.FlowKt", // Deprecated
+    "kotlinx.coroutines.reactive.FlowKt__MigrationKt", // Deprecated
+    "kotlinx.coroutines.reactive.ConvertKt" // Deprecated
+)
+
+tasks.koverHtmlReport {
+    excludes = commonKoverExcludes
+}
+
+tasks.koverVerify {
+    excludes = commonKoverExcludes
+}
diff --git a/reactive/kotlinx-coroutines-reactive/src/Await.kt b/reactive/kotlinx-coroutines-reactive/src/Await.kt
index fef1205..3d9a0f8 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Await.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Await.kt
@@ -106,7 +106,7 @@
 @Deprecated(
     message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " +
         "Please consider using awaitFirstOrDefault().",
-    level = DeprecationLevel.WARNING
+    level = DeprecationLevel.ERROR
 ) // Warning since 1.5, error in 1.6, hidden in 1.7
 public suspend fun <T> Publisher<T>.awaitSingleOrDefault(default: T): T = awaitOne(Mode.SINGLE_OR_DEFAULT, default)
 
@@ -135,7 +135,7 @@
     message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " +
         "There is a specialized version for Reactor's Mono, please use that where applicable. " +
         "Alternatively, please consider using awaitFirstOrNull().",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingleOrNull()", "kotlinx.coroutines.reactor")
 ) // Warning since 1.5, error in 1.6, hidden in 1.7
 public suspend fun <T> Publisher<T>.awaitSingleOrNull(): T? = awaitOne(Mode.SINGLE_OR_DEFAULT)
@@ -164,7 +164,7 @@
 @Deprecated(
     message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " +
         "Please consider using awaitFirstOrElse().",
-    level = DeprecationLevel.WARNING
+    level = DeprecationLevel.ERROR
 ) // Warning since 1.5, error in 1.6, hidden in 1.7
 public suspend fun <T> Publisher<T>.awaitSingleOrElse(defaultValue: () -> T): T =
     awaitOne(Mode.SINGLE_OR_DEFAULT) ?: defaultValue()
@@ -198,12 +198,20 @@
             /** cancelling the new subscription due to rule 2.5, though the publisher would either have to
              * subscribe more than once, which would break 2.12, or leak this [Subscriber]. */
             if (subscription != null) {
-                sub.cancel()
+                withSubscriptionLock {
+                    sub.cancel()
+                }
                 return
             }
             subscription = sub
-            cont.invokeOnCancellation { sub.cancel() }
-            sub.request(if (mode == Mode.FIRST || mode == Mode.FIRST_OR_DEFAULT) 1 else Long.MAX_VALUE)
+            cont.invokeOnCancellation {
+                withSubscriptionLock {
+                    sub.cancel()
+                }
+            }
+            withSubscriptionLock {
+                sub.request(if (mode == Mode.FIRST || mode == Mode.FIRST_OR_DEFAULT) 1 else Long.MAX_VALUE)
+            }
         }
 
         override fun onNext(t: T) {
@@ -228,12 +236,16 @@
                         return
                     }
                     seenValue = true
-                    sub.cancel()
+                    withSubscriptionLock {
+                        sub.cancel()
+                    }
                     cont.resume(t)
                 }
                 Mode.LAST, Mode.SINGLE, Mode.SINGLE_OR_DEFAULT -> {
                     if ((mode == Mode.SINGLE || mode == Mode.SINGLE_OR_DEFAULT) && seenValue) {
-                        sub.cancel()
+                        withSubscriptionLock {
+                            sub.cancel()
+                        }
                         /* the check for `cont.isActive` is needed in case `sub.cancel() above calls `onComplete` or
                          `onError` on its own. */
                         if (cont.isActive) {
@@ -289,6 +301,14 @@
             inTerminalState = true
             return true
         }
+
+        /**
+         * Enforce rule 2.7: [Subscription.request] and [Subscription.cancel] must be executed serially
+         */
+        @Synchronized
+        private fun withSubscriptionLock(block: () -> Unit) {
+            block()
+        }
     })
 }
 
diff --git a/reactive/kotlinx-coroutines-reactive/src/Channel.kt b/reactive/kotlinx-coroutines-reactive/src/Channel.kt
index b7fbf13..a8db217 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Channel.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Channel.kt
@@ -11,29 +11,6 @@
 import org.reactivestreams.*
 
 /**
- * Subscribes to this [Publisher] and returns a channel to receive the elements emitted by it.
- * The resulting channel needs to be [cancelled][ReceiveChannel.cancel] in order to unsubscribe from this publisher.
-
- * @param request how many items to request from the publisher in advance (optional, a single element by default).
- *
- * This method is deprecated in the favor of [Flow].
- * Instead of iterating over the resulting channel please use [collect][Flow.collect]:
- * ```
- * asFlow().collect { value ->
- *     // process value
- * }
- * ```
- */
-@Deprecated(
-    message = "Transforming publisher to channel is deprecated, use asFlow() instead",
-    level = DeprecationLevel.ERROR) // Will be error in 1.4
-public fun <T> Publisher<T>.openSubscription(request: Int = 1): ReceiveChannel<T> {
-    val channel = SubscriptionChannel<T>(request)
-    subscribe(channel)
-    return channel
-}
-
-/**
  * Subscribes to this [Publisher] and performs the specified action for each received element.
  *
  * If [action] throws an exception at some point, the subscription is cancelled, and the exception is rethrown from
@@ -123,3 +100,12 @@
     }
 }
 
+/** @suppress */
+@Deprecated(
+    message = "Transforming publisher to channel is deprecated, use asFlow() instead",
+    level = DeprecationLevel.HIDDEN) // ERROR in 1.4, HIDDEN in 1.6.0
+public fun <T> Publisher<T>.openSubscription(request: Int = 1): ReceiveChannel<T> {
+    val channel = SubscriptionChannel<T>(request)
+    subscribe(channel)
+    return channel
+}
diff --git a/reactive/kotlinx-coroutines-reactive/src/Convert.kt b/reactive/kotlinx-coroutines-reactive/src/Convert.kt
index 3cb05b6..9492b49 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Convert.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Convert.kt
@@ -8,15 +8,9 @@
 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.
- * @param context -- the coroutine context from which the resulting observable is going to be signalled
- */
+/** @suppress */
 @Deprecated(message = "Deprecated in the favour of consumeAsFlow()",
-    level = DeprecationLevel.ERROR, // Error in 1.4
+    level = DeprecationLevel.HIDDEN, // Error in 1.4, HIDDEN in 1.6.0
     replaceWith = ReplaceWith("this.consumeAsFlow().asPublisher(context)", imports = ["kotlinx.coroutines.flow.consumeAsFlow"]))
 public fun <T> ReceiveChannel<T>.asPublisher(context: CoroutineContext = EmptyCoroutineContext): Publisher<T> = publish(context) {
     for (t in this@asPublisher)
diff --git a/reactive/kotlinx-coroutines-reactive/src/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/Publish.kt
index 4928a74..1b8683c 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Publish.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Publish.kt
@@ -6,6 +6,7 @@
 import kotlinx.atomicfu.*
 import kotlinx.coroutines.*
 import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.intrinsics.*
 import kotlinx.coroutines.selects.*
 import kotlinx.coroutines.sync.*
 import org.reactivestreams.*
@@ -104,10 +105,21 @@
     // 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) {
+        val clause =  suspend {
             doLockedNext(element)?.let { throw it }
             block(this)
         }
+
+        launch(start = CoroutineStart.UNDISPATCHED) {
+            mutex.lock()
+            // Already selected -- bail out
+            if (!select.trySelect()) {
+                mutex.unlock()
+                return@launch
+            }
+
+            clause.startCoroutineCancellable(select.completion)
+        }
     }
 
     /*
diff --git a/reactive/kotlinx-coroutines-reactive/test/AwaitCancellationStressTest.kt b/reactive/kotlinx-coroutines-reactive/test/AwaitCancellationStressTest.kt
new file mode 100644
index 0000000..aaf8df6
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/AwaitCancellationStressTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-2020 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.*
+import java.util.concurrent.locks.*
+
+/**
+ * This test checks implementation of rule 2.7 for await methods - serial execution of subscription methods
+ */
+class AwaitCancellationStressTest : TestBase() {
+    private val iterations = 10_000 * stressTestMultiplier
+
+    @Test
+    fun testAwaitCancellationOrder() = runTest {
+        repeat(iterations) {
+            val job = launch(Dispatchers.Default) {
+                testPublisher().awaitFirst()
+            }
+            job.cancelAndJoin()
+        }
+    }
+
+    private fun testPublisher() = Publisher<Int> { s ->
+        val lock = ReentrantLock()
+        s.onSubscribe(object : Subscription {
+            override fun request(n: Long) {
+                check(lock.tryLock())
+                s.onNext(42)
+                lock.unlock()
+            }
+
+            override fun cancel() {
+                check(lock.tryLock())
+                lock.unlock()
+            }
+        })
+    }
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
index efe7ec7..fa03989 100644
--- a/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
@@ -52,9 +52,6 @@
         assertEquals("ELSE", pub.awaitFirstOrElse { "ELSE" })
         assertFailsWith<NoSuchElementException> { pub.awaitLast() }
         assertFailsWith<NoSuchElementException> { pub.awaitSingle() }
-        assertEquals("OK", pub.awaitSingleOrDefault("OK"))
-        assertNull(pub.awaitSingleOrNull())
-        assertEquals("ELSE", pub.awaitSingleOrElse { "ELSE" })
         var cnt = 0
         pub.collect { cnt++ }
         assertEquals(0, cnt)
@@ -72,9 +69,6 @@
         assertEquals("OK", pub.awaitFirstOrElse { "ELSE" })
         assertEquals("OK", pub.awaitLast())
         assertEquals("OK", pub.awaitSingle())
-        assertEquals("OK", pub.awaitSingleOrDefault("!"))
-        assertEquals("OK", pub.awaitSingleOrNull())
-        assertEquals("OK", pub.awaitSingleOrElse { "ELSE" })
         var cnt = 0
         pub.collect {
             assertEquals("OK", it)
@@ -189,10 +183,6 @@
             onError(dummyThrowable)
             onComplete()
         }
-        assertDetectsBadPublisher<Int>({ awaitSingleOrDefault(2) }, "terminal state") {
-            onComplete()
-            onError(dummyThrowable)
-        }
         assertDetectsBadPublisher<Int>({ awaitFirst() }, "terminal state") {
             onNext(0)
             onComplete()
@@ -251,4 +241,4 @@
             it
         }
     }
-}
\ No newline at end of file
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
index 095b724..d92a888 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
@@ -5,9 +5,13 @@
 package kotlinx.coroutines.reactive
 
 import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.sync.*
 import org.junit.Test
 import org.reactivestreams.*
+import java.util.concurrent.*
 import kotlin.test.*
 
 class PublishTest : TestBase() {
@@ -278,10 +282,40 @@
         val publisher = publish {
             assertFailsWith<NullPointerException> { send(null) }
             assertFailsWith<NullPointerException> { trySend(null) }
-            @Suppress("DEPRECATION")
-            assertFailsWith<NullPointerException> { offer(null) }
             send("OK")
         }
         assertEquals("OK", publisher.awaitFirstOrNull())
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testOnSendCancelled() = runTest {
+        val latch = CountDownLatch(1)
+        val published = publish(Dispatchers.Default) {
+            expect(2)
+            // Collector is ready
+            send(1)
+            try {
+                send(2)
+                expectUnreached()
+            } catch (e: CancellationException) {
+                // publisher cancellation is async
+                latch.countDown()
+                throw e
+            }
+        }
+
+        expect(1)
+        val collectorLatch = Mutex(true)
+        val job = launch {
+            published.asFlow().buffer(0).collect {
+                collectorLatch.unlock()
+                hang { expect(4) }
+            }
+        }
+        collectorLatch.lock()
+        expect(3)
+        job.cancelAndJoin()
+        latch.await()
+        finish(5)
+    }
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt
index e3b1d3b..4a552b5 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt
@@ -5,6 +5,7 @@
 package kotlinx.coroutines.reactive
 
 import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
 import org.junit.Test
 import kotlin.test.*
 
@@ -16,7 +17,7 @@
             // concurrent emitters (many coroutines)
             val jobs = List(n) {
                 // launch
-                launch {
+                launch(Dispatchers.Default) {
                     send(it)
                 }
             }
@@ -28,4 +29,26 @@
         }
         assertEquals(n, resultSet.size)
     }
+
+    @Test
+    fun testConcurrentStressOnSend() = runBlocking {
+        val n = 10_000 * stressTestMultiplier
+        val observable = publish<Int> {
+            // concurrent emitters (many coroutines)
+            val jobs = List(n) {
+                // launch
+                launch(Dispatchers.Default) {
+                    select<Unit> {
+                        onSend(it) {}
+                    }
+                }
+            }
+            jobs.forEach { it.join() }
+        }
+        val resultSet = mutableSetOf<Int>()
+        observable.collect {
+            assertTrue(resultSet.add(it))
+        }
+        assertEquals(n, resultSet.size)
+    }
 }
diff --git a/reactive/kotlinx-coroutines-reactor/README.md b/reactive/kotlinx-coroutines-reactor/README.md
index 69157af..577385a 100644
--- a/reactive/kotlinx-coroutines-reactor/README.md
+++ b/reactive/kotlinx-coroutines-reactor/README.md
@@ -32,24 +32,24 @@
 <!--- 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
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[CoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
 
 <!--- INDEX kotlinx.coroutines.channels -->
 <!--- INDEX kotlinx.coroutines.flow -->
 
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+[Flow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
 
 <!--- MODULE kotlinx-coroutines-reactor -->
 <!--- INDEX kotlinx.coroutines.reactor -->
 
-[mono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/mono.html
-[flux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/flux.html
-[Flow.asFlux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-flux.html
-[ReactorContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/-reactor-context/index.html
-[kotlinx.coroutines.Job.asMono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-mono.html
-[kotlinx.coroutines.Deferred.asMono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-mono.html
-[reactor.core.scheduler.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-coroutine-dispatcher.html
+[mono]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/mono.html
+[flux]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/flux.html
+[Flow.asFlux]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-flux.html
+[ReactorContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/-reactor-context/index.html
+[kotlinx.coroutines.Job.asMono]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-mono.html
+[kotlinx.coroutines.Deferred.asMono]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-mono.html
+[reactor.core.scheduler.Scheduler.asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-coroutine-dispatcher.html
 
 <!--- END -->
 
diff --git a/reactive/kotlinx-coroutines-reactor/build.gradle.kts b/reactive/kotlinx-coroutines-reactor/build.gradle.kts
index 03af7f4..d4bb135 100644
--- a/reactive/kotlinx-coroutines-reactor/build.gradle.kts
+++ b/reactive/kotlinx-coroutines-reactor/build.gradle.kts
@@ -5,8 +5,8 @@
 val reactorVersion = version("reactor")
 
 dependencies {
-    compile("io.projectreactor:reactor-core:$reactorVersion")
-    compile(project(":kotlinx-coroutines-reactive"))
+    api("io.projectreactor:reactor-core:$reactorVersion")
+    api(project(":kotlinx-coroutines-reactive"))
 }
 
 java {
@@ -27,3 +27,16 @@
 externalDocumentationLink(
     url = "https://projectreactor.io/docs/core/$reactorVersion/api/"
 )
+
+val commonKoverExcludes = listOf(
+    "kotlinx.coroutines.reactor.FlowKt", // Deprecated
+    "kotlinx.coroutines.reactor.ConvertKt\$asFlux$1" // Deprecated
+)
+
+tasks.koverHtmlReport {
+    excludes = commonKoverExcludes
+}
+
+tasks.koverVerify {
+    excludes = commonKoverExcludes
+}
diff --git a/reactive/kotlinx-coroutines-reactor/src/Mono.kt b/reactive/kotlinx-coroutines-reactor/src/Mono.kt
index e86d51c..f31004b 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Mono.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Mono.kt
@@ -157,7 +157,7 @@
 @Deprecated(
     message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " +
         "Please use awaitSingle() instead.",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingle()")
 ) // Warning since 1.5, error in 1.6
 public suspend fun <T> Mono<T>.awaitFirst(): T = awaitSingle()
@@ -181,7 +181,7 @@
 @Deprecated(
     message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " +
         "Please use awaitSingleOrNull() instead.",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default")
 ) // Warning since 1.5, error in 1.6
 public suspend fun <T> Mono<T>.awaitFirstOrDefault(default: T): T = awaitSingleOrNull() ?: default
@@ -205,7 +205,7 @@
 @Deprecated(
     message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " +
         "Please use awaitSingleOrNull() instead.",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingleOrNull()")
 ) // Warning since 1.5, error in 1.6
 public suspend fun <T> Mono<T>.awaitFirstOrNull(): T? = awaitSingleOrNull()
@@ -229,7 +229,7 @@
 @Deprecated(
     message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " +
         "Please use awaitSingleOrNull() instead.",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: defaultValue()")
 ) // Warning since 1.5, error in 1.6
 public suspend fun <T> Mono<T>.awaitFirstOrElse(defaultValue: () -> T): T = awaitSingleOrNull() ?: defaultValue()
@@ -253,7 +253,7 @@
 @Deprecated(
     message = "Mono produces at most one value, so the last element is the same as the first. " +
         "Please use awaitSingle() instead.",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingle()")
 ) // Warning since 1.5, error in 1.6
 public suspend fun <T> Mono<T>.awaitLast(): T = awaitSingle()
diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
index d922840..912fb6e 100644
--- a/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
@@ -4,7 +4,6 @@
 
 package kotlinx.coroutines.reactor
 
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlin.coroutines.*
 import kotlinx.coroutines.reactive.*
 import reactor.util.context.*
@@ -65,11 +64,7 @@
  */
 public fun ContextView.asCoroutineContext(): ReactorContext = ReactorContext(this)
 
-/**
- * Wraps the given [Context] into [ReactorContext], so it can be added to the coroutine's context
- * and later used via `coroutineContext[ReactorContext]`.
- * @suppress
- */
+/** @suppress */
 @Deprecated("The more general version for ContextView should be used instead", level = DeprecationLevel.HIDDEN)
 public fun Context.asCoroutineContext(): ReactorContext = readOnly().asCoroutineContext() // `readOnly()` is zero-cost.
 
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt
index cc336ba..3879c62 100644
--- a/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt
@@ -69,72 +69,6 @@
     }
 
     @Test
-    fun testAwaitSingleOrDefault() {
-        val flux = flux {
-            send(Flux.empty<String>().awaitSingleOrDefault("O") + "K")
-        }
-
-        checkSingleValue(flux) {
-            assertEquals("OK", it)
-        }
-    }
-
-    @Test
-    fun testAwaitSingleOrDefaultException() {
-        val flux = flux {
-            send(Flux.just("O", "#").awaitSingleOrDefault("!") + "K")
-        }
-
-        checkErroneous(flux) {
-            assert(it is IllegalArgumentException)
-        }
-    }
-
-    @Test
-    fun testAwaitSingleOrNull() {
-        val flux = flux<String?> {
-            send(Flux.empty<String>().awaitSingleOrNull() ?: "OK")
-        }
-
-        checkSingleValue(flux) {
-            assertEquals("OK", it)
-        }
-    }
-
-    @Test
-    fun testAwaitSingleOrNullException() {
-        val flux = flux {
-            send((Flux.just("O", "#").awaitSingleOrNull() ?: "!") + "K")
-        }
-
-        checkErroneous(flux) {
-            assert(it is IllegalArgumentException)
-        }
-    }
-
-    @Test
-    fun testAwaitSingleOrElse() {
-        val flux = flux {
-            send(Flux.empty<String>().awaitSingleOrElse { "O" } + "K")
-        }
-
-        checkSingleValue(flux) {
-            assertEquals("OK", it)
-        }
-    }
-
-    @Test
-    fun testAwaitSingleOrElseException() {
-        val flux = flux {
-            send(Flux.just("O", "#").awaitSingleOrElse { "!" } + "K")
-        }
-
-        checkErroneous(flux) {
-            assert(it is IllegalArgumentException)
-        }
-    }
-
-    @Test
     fun testAwaitFirst() {
         val flux = flux {
             send(Flux.just("O", "#").awaitFirst() + "K")
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
index d059eb6..f575af4 100644
--- a/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
@@ -170,10 +170,8 @@
         val flux = flux {
             assertFailsWith<NullPointerException> { send(null) }
             assertFailsWith<NullPointerException> { trySend(null) }
-            @Suppress("DEPRECATION")
-            assertFailsWith<NullPointerException> { offer(null) }
             send("OK")
         }
         assertEquals("OK", flux.awaitFirstOrNull())
     }
-}
\ No newline at end of file
+}
diff --git a/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt b/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
index 421295d..2a5e5dc 100644
--- a/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
@@ -119,29 +119,6 @@
         assertNull(Mono.empty<Int>().awaitSingleOrNull())
     }
 
-    /** Tests that the versions of the await methods specialized for Mono for deprecation behave correctly and we don't
-     * break any code by introducing them. */
-    @Test
-    @Suppress("DEPRECATION")
-    fun testDeprecatedAwaitMethods() = runBlocking {
-        val filledMono = mono<String> { "OK" }
-        assertEquals("OK", filledMono.awaitFirst())
-        assertEquals("OK", filledMono.awaitFirstOrDefault("!"))
-        assertEquals("OK", filledMono.awaitFirstOrNull())
-        assertEquals("OK", filledMono.awaitFirstOrElse { "ELSE" })
-        assertEquals("OK", filledMono.awaitLast())
-        assertEquals("OK", filledMono.awaitSingleOrDefault("!"))
-        assertEquals("OK", filledMono.awaitSingleOrElse { "ELSE" })
-        val emptyMono = mono<String> { null }
-        assertFailsWith<NoSuchElementException> { emptyMono.awaitFirst() }
-        assertEquals("OK", emptyMono.awaitFirstOrDefault("OK"))
-        assertNull(emptyMono.awaitFirstOrNull())
-        assertEquals("ELSE", emptyMono.awaitFirstOrElse { "ELSE" })
-        assertFailsWith<NoSuchElementException> { emptyMono.awaitLast() }
-        assertEquals("OK", emptyMono.awaitSingleOrDefault("OK"))
-        assertEquals("ELSE", emptyMono.awaitSingleOrElse { "ELSE" })
-    }
-
     /** Tests that calls to [awaitSingleOrNull] (and, thus, to the rest of such functions) throw [CancellationException]
      * and unsubscribe from the publisher when their [Job] is cancelled. */
     @Test
diff --git a/reactive/kotlinx-coroutines-rx2/README.md b/reactive/kotlinx-coroutines-rx2/README.md
index d93f569..4131716 100644
--- a/reactive/kotlinx-coroutines-rx2/README.md
+++ b/reactive/kotlinx-coroutines-rx2/README.md
@@ -50,40 +50,40 @@
 <!--- 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
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[CoroutineDispatcher]: https://kotlinlang.org/api/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
+[ProducerScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
 
 <!--- INDEX kotlinx.coroutines.flow -->
 
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+[Flow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
 
 <!--- MODULE kotlinx-coroutines-rx2 -->
 <!--- INDEX kotlinx.coroutines.rx2 -->
 
-[rxCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-completable.html
-[rxMaybe]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-maybe.html
-[rxSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-single.html
-[rxObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-observable.html
-[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-flowable.html
-[Flow.asFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-flowable.html
-[Flow.asObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-observable.html
-[ObservableSource.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-flow.html
-[io.reactivex.CompletableSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await.html
-[io.reactivex.MaybeSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single.html
-[io.reactivex.MaybeSource.awaitSingleOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single-or-null.html
-[io.reactivex.SingleSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await.html
-[io.reactivex.ObservableSource.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first.html
-[io.reactivex.ObservableSource.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-default.html
-[io.reactivex.ObservableSource.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-else.html
-[io.reactivex.ObservableSource.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-null.html
-[io.reactivex.ObservableSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single.html
-[kotlinx.coroutines.Job.asCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-completable.html
-[kotlinx.coroutines.Deferred.asSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-single.html
-[io.reactivex.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-coroutine-dispatcher.html
+[rxCompletable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-completable.html
+[rxMaybe]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-maybe.html
+[rxSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-single.html
+[rxObservable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-observable.html
+[rxFlowable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-flowable.html
+[Flow.asFlowable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-flowable.html
+[Flow.asObservable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-observable.html
+[ObservableSource.asFlow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-flow.html
+[io.reactivex.CompletableSource.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await.html
+[io.reactivex.MaybeSource.awaitSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single.html
+[io.reactivex.MaybeSource.awaitSingleOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single-or-null.html
+[io.reactivex.SingleSource.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await.html
+[io.reactivex.ObservableSource.awaitFirst]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first.html
+[io.reactivex.ObservableSource.awaitFirstOrDefault]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-default.html
+[io.reactivex.ObservableSource.awaitFirstOrElse]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-else.html
+[io.reactivex.ObservableSource.awaitFirstOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-null.html
+[io.reactivex.ObservableSource.awaitSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single.html
+[kotlinx.coroutines.Job.asCompletable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-completable.html
+[kotlinx.coroutines.Deferred.asSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-single.html
+[io.reactivex.Scheduler.asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-coroutine-dispatcher.html
 
 <!--- END -->
 
diff --git a/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api b/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api
index c27ef4d..c2d1c4b 100644
--- a/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api
+++ b/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api
@@ -16,8 +16,8 @@
 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 openSubscription (Lio/reactivex/MaybeSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
-	public static final fun openSubscription (Lio/reactivex/ObservableSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
+	public static final synthetic fun openSubscription (Lio/reactivex/MaybeSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
+	public static final synthetic fun openSubscription (Lio/reactivex/ObservableSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
 	public static final fun toChannel (Lio/reactivex/MaybeSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
 	public static final fun toChannel (Lio/reactivex/ObservableSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
 }
@@ -69,7 +69,9 @@
 }
 
 public final class kotlinx/coroutines/rx2/RxSchedulerKt {
-	public static final fun asCoroutineDispatcher (Lio/reactivex/Scheduler;)Lkotlinx/coroutines/rx2/SchedulerCoroutineDispatcher;
+	public static final fun asCoroutineDispatcher (Lio/reactivex/Scheduler;)Lkotlinx/coroutines/CoroutineDispatcher;
+	public static final synthetic fun asCoroutineDispatcher (Lio/reactivex/Scheduler;)Lkotlinx/coroutines/rx2/SchedulerCoroutineDispatcher;
+	public static final fun asScheduler (Lkotlinx/coroutines/CoroutineDispatcher;)Lio/reactivex/Scheduler;
 }
 
 public final class kotlinx/coroutines/rx2/RxSingleKt {
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt
index 0e0b47e..da9809c 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt
@@ -80,7 +80,7 @@
  */
 @Deprecated(
     message = "Deprecated in favor of awaitSingleOrNull()",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingleOrNull()")
 ) // Warning since 1.5, error in 1.6, hidden in 1.7
 public suspend fun <T> MaybeSource<T>.await(): T? = awaitSingleOrNull()
@@ -102,7 +102,7 @@
  */
 @Deprecated(
     message = "Deprecated in favor of awaitSingleOrNull()",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default")
 ) // Warning since 1.5, error in 1.6, hidden in 1.7
 public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt
index bb093b0..fc09bf9 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt
@@ -13,36 +13,6 @@
 import kotlinx.coroutines.reactive.*
 
 /**
- * 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.
- *
- * This API is deprecated in the favour of [Flow].
- * [MaybeSource] doesn't have a corresponding [Flow] adapter, so it should be transformed to [Observable] first.
- * @suppress
- */
-@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.ERROR) // Will be hidden in 1.5
-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.
- *
- * This API is deprecated in the favour of [Flow].
- * [ObservableSource] doesn't have a corresponding [Flow] adapter, so it should be transformed to [Observable] first.
- * @suppress
- */
-@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.ERROR) // Will be hidden in 1.5
-public fun <T> ObservableSource<T>.openSubscription(): ReceiveChannel<T> {
-    val channel = SubscriptionChannel<T>()
-    subscribe(channel)
-    return channel
-}
-
-/**
  * Subscribes to this [MaybeSource] and performs the specified action for each received element.
  *
  * If [action] throws an exception at some point or if the [MaybeSource] raises an error, the exception is rethrown from
@@ -107,3 +77,19 @@
         close(cause = e)
     }
 }
+
+/** @suppress */
+@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.HIDDEN) // ERROR in 1.4.0, HIDDEN in 1.6.0
+public fun <T> ObservableSource<T>.openSubscription(): ReceiveChannel<T> {
+    val channel = SubscriptionChannel<T>()
+    subscribe(channel)
+    return channel
+}
+
+/** @suppress */
+@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.HIDDEN) // ERROR in 1.4.0, HIDDEN in 1.6.0
+public fun <T> MaybeSource<T>.openSubscription(): ReceiveChannel<T> {
+    val channel = SubscriptionChannel<T>()
+    subscribe(channel)
+    return channel
+}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt b/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
index 3f91538..e4670f3 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
@@ -20,7 +20,7 @@
     context: CoroutineContext = EmptyCoroutineContext,
     block: suspend CoroutineScope.() -> Unit
 ): Completable {
-    require(context[Job] === null) { "Completable context cannot contain job in it." +
+    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)
 }
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
index 5f40981..90e770b 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
@@ -10,6 +10,7 @@
 import kotlinx.coroutines.*
 import kotlinx.coroutines.channels.*
 import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
 import kotlinx.coroutines.selects.*
 import kotlinx.coroutines.sync.*
 import kotlin.coroutines.*
@@ -95,10 +96,22 @@
         element: T,
         block: suspend (SendChannel<T>) -> R
     ) {
-        mutex.onLock.registerSelectClause2(select, null) {
+        val clause =  suspend {
             doLockedNext(element)?.let { throw it }
             block(this)
         }
+
+        // This is the default replacement proposed in onLock replacement
+        launch(start = CoroutineStart.UNDISPATCHED) {
+            mutex.lock()
+            // Already selected -- bail out
+            if (!select.trySelect()) {
+                mutex.unlock()
+                return@launch
+            }
+
+            clause.startCoroutineCancellable(select.completion)
+        }
     }
 
     // assert: mutex.isLocked()
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt b/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt
index 0262fc1..d7d5f6c 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt
@@ -4,16 +4,143 @@
 
 package kotlinx.coroutines.rx2
 
-import io.reactivex.Scheduler
+import io.reactivex.*
+import io.reactivex.disposables.*
+import io.reactivex.plugins.*
+import kotlinx.atomicfu.*
 import kotlinx.coroutines.*
-import java.util.concurrent.TimeUnit
-import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
 
 /**
  * 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)
+public fun Scheduler.asCoroutineDispatcher(): CoroutineDispatcher =
+    if (this is DispatcherScheduler) {
+        dispatcher
+    } else {
+        SchedulerCoroutineDispatcher(this)
+    }
+
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.4.2, binary compatibility with earlier versions")
+@JvmName("asCoroutineDispatcher")
+public fun Scheduler.asCoroutineDispatcher0(): SchedulerCoroutineDispatcher =
+    SchedulerCoroutineDispatcher(this)
+
+/**
+ * Converts an instance of [CoroutineDispatcher] to an implementation of [Scheduler].
+ */
+public fun CoroutineDispatcher.asScheduler(): Scheduler =
+    if (this is SchedulerCoroutineDispatcher) {
+        scheduler
+    } else {
+        DispatcherScheduler(this)
+    }
+
+private class DispatcherScheduler(@JvmField val dispatcher: CoroutineDispatcher) : Scheduler() {
+
+    private val schedulerJob = SupervisorJob()
+
+    /**
+     * The scope for everything happening in this [DispatcherScheduler].
+     *
+     * Running tasks, too, get launched under this scope, because [shutdown] should cancel the running tasks as well.
+     */
+    private val scope = CoroutineScope(schedulerJob + dispatcher)
+
+    /**
+     * The counter of created workers, for their pretty-printing.
+     */
+    private val workerCounter = atomic(1L)
+
+    override fun scheduleDirect(block: Runnable, delay: Long, unit: TimeUnit): Disposable =
+        scope.scheduleTask(block, unit.toMillis(delay)) { task ->
+            Runnable { scope.launch { task() } }
+        }
+
+    override fun createWorker(): Worker = DispatcherWorker(workerCounter.getAndIncrement(), dispatcher, schedulerJob)
+
+    override fun shutdown() {
+        schedulerJob.cancel()
+    }
+
+    private class DispatcherWorker(
+        private val counter: Long,
+        private val dispatcher: CoroutineDispatcher,
+        parentJob: Job
+    ) : Worker() {
+
+        private val workerJob = SupervisorJob(parentJob)
+        private val workerScope = CoroutineScope(workerJob + dispatcher)
+        private val blockChannel = Channel<suspend () -> Unit>(Channel.UNLIMITED)
+
+        init {
+            workerScope.launch {
+                blockChannel.consumeEach {
+                    it()
+                }
+            }
+        }
+
+        override fun schedule(block: Runnable, delay: Long, unit: TimeUnit): Disposable =
+            workerScope.scheduleTask(block, unit.toMillis(delay)) { task ->
+                Runnable { blockChannel.trySend(task) }
+            }
+
+        override fun isDisposed(): Boolean = !workerScope.isActive
+
+        override fun dispose() {
+            blockChannel.close()
+            workerJob.cancel()
+        }
+
+        override fun toString(): String = "$dispatcher (worker $counter, ${if (isDisposed) "disposed" else "active"})"
+    }
+
+    override fun toString(): String = dispatcher.toString()
+}
+
+private typealias Task = suspend () -> Unit
+
+/**
+ * Schedule [block] so that an adapted version of it, wrapped in [adaptForScheduling], executes after [delayMillis]
+ * milliseconds.
+ */
+private fun CoroutineScope.scheduleTask(
+    block: Runnable,
+    delayMillis: Long,
+    adaptForScheduling: (Task) -> Runnable
+): Disposable {
+    val ctx = coroutineContext
+    var handle: DisposableHandle? = null
+    val disposable = Disposables.fromRunnable {
+        // null if delay <= 0
+        handle?.dispose()
+    }
+    val decoratedBlock = RxJavaPlugins.onSchedule(block)
+    suspend fun task() {
+        if (disposable.isDisposed) return
+        try {
+            runInterruptible {
+                decoratedBlock.run()
+            }
+        } catch (e: Throwable) {
+            handleUndeliverableException(e, ctx)
+        }
+    }
+
+    val toSchedule = adaptForScheduling(::task)
+    if (!isActive) return Disposables.disposed()
+    if (delayMillis <= 0) {
+        toSchedule.run()
+    } else {
+        @Suppress("INVISIBLE_MEMBER")
+        ctx.delay.invokeOnTimeout(delayMillis, toSchedule, ctx).let { handle = it }
+    }
+    return disposable
+}
 
 /**
  * Implements [CoroutineDispatcher] on top of an arbitrary [Scheduler].
@@ -45,8 +172,10 @@
 
     /** @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)
-}
+}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableCompletionStressTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableCompletionStressTest.kt
index 30266e3..7e1d335 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ObservableCompletionStressTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableCompletionStressTest.kt
@@ -12,7 +12,7 @@
 class ObservableCompletionStressTest : TestBase() {
     private val N_REPEATS = 10_000 * stressTestMultiplier
 
-    private fun CoroutineScope.range(context: CoroutineContext, start: Int, count: Int) = rxObservable(context) {
+    private fun range(context: CoroutineContext, start: Int, count: Int) = rxObservable(context) {
         for (x in start until start + count) send(x)
     }
 
@@ -33,4 +33,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt
index 074fcf4..7023211 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt
@@ -6,6 +6,7 @@
 
 import io.reactivex.*
 import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
 import org.junit.Test
 import java.io.*
 import kotlin.test.*
@@ -48,6 +49,29 @@
     }
 
     @Test
+    fun testConcurrentStressOnSend() {
+        val n = 10_000 * stressTestMultiplier
+        val observable = rxObservable<Int> {
+            newCoroutineContext(coroutineContext)
+            // concurrent emitters (many coroutines)
+            val jobs = List(n) {
+                // launch
+                launch(Dispatchers.Default) {
+                    val i = it
+                    select<Unit> {
+                        onSend(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 = rxObservable(Dispatchers.Unconfined) {
@@ -88,4 +112,4 @@
             assertEquals("OK", it)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/SchedulerStressTest.kt b/reactive/kotlinx-coroutines-rx2/test/SchedulerStressTest.kt
new file mode 100644
index 0000000..ea33a9f
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/SchedulerStressTest.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.rx2
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.util.concurrent.*
+
+class SchedulerStressTest : TestBase() {
+    @Before
+    fun setup() {
+        ignoreLostThreads("RxCachedThreadScheduler-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+    }
+
+    /**
+     * Test that we don't get an OOM if we schedule many jobs at once.
+     * It's expected that if you don't dispose you'd see an OOM error.
+     */
+    @Test
+    fun testSchedulerDisposed(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableDisposed(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerDisposed(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        val worker = scheduler.createWorker()
+        testRunnableDisposed(worker::schedule)
+    }
+
+    private suspend fun testRunnableDisposed(block: RxSchedulerBlockNoDelay) {
+        val n = 2000 * stressTestMultiplier
+        repeat(n) {
+            val a = ByteArray(1000000) //1MB
+            val disposable = block(Runnable {
+                keepMe(a)
+                expectUnreached()
+            })
+            disposable.dispose()
+            yield() // allow the scheduled task to observe that it was disposed
+        }
+    }
+
+    /**
+     * Test function that holds a reference. Used for testing OOM situations
+     */
+    private fun keepMe(a: ByteArray) {
+        Thread.sleep(a.size / (a.size + 1) + 10L)
+    }
+
+    /**
+     * Test that we don't get an OOM if we schedule many delayed jobs at once. It's expected that if you don't dispose that you'd
+     * see a OOM error.
+     */
+    @Test
+    fun testSchedulerDisposedDuringDelay(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableDisposedDuringDelay(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerDisposedDuringDelay(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        val worker = scheduler.createWorker()
+        testRunnableDisposedDuringDelay(worker::schedule)
+    }
+
+    private fun testRunnableDisposedDuringDelay(block: RxSchedulerBlockWithDelay) {
+        val n = 2000 * stressTestMultiplier
+        repeat(n) {
+            val a = ByteArray(1000000) //1MB
+            val delayMillis: Long = 10
+            val disposable = block(Runnable {
+                keepMe(a)
+                expectUnreached()
+            }, delayMillis, TimeUnit.MILLISECONDS)
+            disposable.dispose()
+        }
+    }
+}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/SchedulerTest.kt b/reactive/kotlinx-coroutines-rx2/test/SchedulerTest.kt
index 26dbe8f..1941867 100644
--- a/reactive/kotlinx-coroutines-rx2/test/SchedulerTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/SchedulerTest.kt
@@ -4,10 +4,18 @@
 
 package kotlinx.coroutines.rx2
 
-import io.reactivex.schedulers.Schedulers
+import io.reactivex.*
+import io.reactivex.disposables.*
+import io.reactivex.plugins.*
+import io.reactivex.schedulers.*
 import kotlinx.coroutines.*
-import org.junit.Before
+import kotlinx.coroutines.sync.*
+import org.junit.*
 import org.junit.Test
+import java.lang.Runnable
+import java.util.concurrent.*
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.*
 import kotlin.test.*
 
 class SchedulerTest : TestBase() {
@@ -17,7 +25,7 @@
     }
 
     @Test
-    fun testIoScheduler(): Unit = runBlocking {
+    fun testIoScheduler(): Unit = runTest {
         expect(1)
         val mainThread = Thread.currentThread()
         withContext(Schedulers.io().asCoroutineDispatcher()) {
@@ -31,4 +39,458 @@
         }
         finish(4)
     }
-}
\ No newline at end of file
+
+    /** Tests [toString] implementations of [CoroutineDispatcher.asScheduler] and its [Scheduler.Worker]. */
+    @Test
+    fun testSchedulerToString() {
+        val name = "Dispatchers.Default"
+        val scheduler = Dispatchers.Default.asScheduler()
+        assertContains(scheduler.toString(), name)
+        val worker = scheduler.createWorker()
+        val activeWorkerName = worker.toString()
+        assertContains(worker.toString(), name)
+        worker.dispose()
+        val disposedWorkerName = worker.toString()
+        assertNotEquals(activeWorkerName, disposedWorkerName)
+    }
+
+    private fun runSchedulerTest(nThreads: Int = 1, action: (Scheduler) -> Unit) {
+        val future = CompletableFuture<Unit>()
+        try {
+            newFixedThreadPoolContext(nThreads, "test").use { dispatcher ->
+                RxJavaPlugins.setErrorHandler {
+                    if (!future.completeExceptionally(it)) {
+                        handleUndeliverableException(it, dispatcher)
+                    }
+                }
+                action(dispatcher.asScheduler())
+            }
+        } finally {
+            RxJavaPlugins.setErrorHandler(null)
+        }
+        future.complete(Unit)
+        future.getNow(Unit) // rethrow any encountered errors
+    }
+
+    private fun ensureSeparateThread(schedule: (Runnable, Long, TimeUnit) -> Unit, scheduleNoDelay: (Runnable) -> Unit) {
+        val mainThread = Thread.currentThread()
+        val cdl1 = CountDownLatch(1)
+        val cdl2 = CountDownLatch(1)
+        expect(1)
+        val thread = AtomicReference<Thread?>(null)
+        fun checkThread() {
+            val current = Thread.currentThread()
+            thread.getAndSet(current)?.let { assertEquals(it, current) }
+        }
+        schedule({
+            assertNotSame(mainThread, Thread.currentThread())
+            checkThread()
+            cdl2.countDown()
+        }, 300, TimeUnit.MILLISECONDS)
+        scheduleNoDelay {
+            expect(2)
+            checkThread()
+            assertNotSame(mainThread, Thread.currentThread())
+            cdl1.countDown()
+        }
+        cdl1.await()
+        cdl2.await()
+        finish(3)
+    }
+
+    /**
+     * Tests [Scheduler.scheduleDirect] for [CoroutineDispatcher.asScheduler] on a single-threaded dispatcher.
+     */
+    @Test
+    fun testSingleThreadedDispatcherDirect(): Unit = runSchedulerTest(1) {
+        ensureSeparateThread(it::scheduleDirect, it::scheduleDirect)
+    }
+
+    /**
+     * Tests [Scheduler.Worker.schedule] for [CoroutineDispatcher.asScheduler] running its tasks on the correct thread.
+     */
+    @Test
+    fun testSingleThreadedWorker(): Unit = runSchedulerTest(1) {
+        val worker = it.createWorker()
+        ensureSeparateThread(worker::schedule, worker::schedule)
+    }
+
+    private fun checkCancelling(schedule: (Runnable, Long, TimeUnit) -> Disposable) {
+        // cancel the task before it has a chance to run.
+        val handle1 = schedule({
+            throw IllegalStateException("should have been successfully cancelled")
+        }, 10_000, TimeUnit.MILLISECONDS)
+        handle1.dispose()
+        // cancel the task after it started running.
+        val cdl1 = CountDownLatch(1)
+        val cdl2 = CountDownLatch(1)
+        val handle2 = schedule({
+            cdl1.countDown()
+            cdl2.await()
+            if (Thread.interrupted())
+                throw IllegalStateException("cancelling the task should not interrupt the thread")
+        }, 100, TimeUnit.MILLISECONDS)
+        cdl1.await()
+        handle2.dispose()
+        cdl2.countDown()
+    }
+
+    /**
+     * Test cancelling [Scheduler.scheduleDirect] for [CoroutineDispatcher.asScheduler].
+     */
+    @Test
+    fun testCancellingDirect(): Unit = runSchedulerTest {
+        checkCancelling(it::scheduleDirect)
+    }
+
+    /**
+     * Test cancelling [Scheduler.Worker.schedule] for [CoroutineDispatcher.asScheduler].
+     */
+    @Test
+    fun testCancellingWorker(): Unit = runSchedulerTest {
+        val worker = it.createWorker()
+        checkCancelling(worker::schedule)
+    }
+
+    /**
+     * Test shutting down [CoroutineDispatcher.asScheduler].
+     */
+    @Test
+    fun testShuttingDown() {
+        val n = 5
+        runSchedulerTest(nThreads = n) { scheduler ->
+            val cdl1 = CountDownLatch(n)
+            val cdl2 = CountDownLatch(1)
+            val cdl3 = CountDownLatch(n)
+            repeat(n) {
+                scheduler.scheduleDirect {
+                    cdl1.countDown()
+                    try {
+                        cdl2.await()
+                    } catch (e: InterruptedException) {
+                        // this is the expected outcome
+                        cdl3.countDown()
+                    }
+                }
+            }
+            cdl1.await()
+            scheduler.shutdown()
+            if (!cdl3.await(1, TimeUnit.SECONDS)) {
+                cdl2.countDown()
+                error("the tasks were not cancelled when the scheduler was shut down")
+            }
+        }
+    }
+
+    /** Tests that there are no uncaught exceptions if [Disposable.dispose] on a worker happens when tasks are present. */
+    @Test
+    fun testDisposingWorker() = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        val worker = scheduler.createWorker()
+        yield() // so that the worker starts waiting on the channel
+        assertFalse(worker.isDisposed)
+        worker.dispose()
+        assertTrue(worker.isDisposed)
+    }
+
+    /** Tests trying to use a [Scheduler.Worker]/[Scheduler] after [Scheduler.Worker.dispose]/[Scheduler.shutdown]. */
+    @Test
+    fun testSchedulingAfterDisposing() = runSchedulerTest {
+        expect(1)
+        val worker = it.createWorker()
+        // use CDL to ensure that the worker has properly initialized
+        val cdl1 = CountDownLatch(1)
+        setScheduler(2, 3)
+        val disposable1 = worker.schedule {
+            cdl1.countDown()
+        }
+        cdl1.await()
+        expect(4)
+        assertFalse(disposable1.isDisposed)
+        setScheduler(6, -1)
+        // check that the worker automatically disposes of the tasks after being disposed
+        assertFalse(worker.isDisposed)
+        worker.dispose()
+        assertTrue(worker.isDisposed)
+        expect(5)
+        val disposable2 = worker.schedule {
+            expectUnreached()
+        }
+        assertTrue(disposable2.isDisposed)
+        setScheduler(7, 8)
+        // ensure that the scheduler still works
+        val cdl2 = CountDownLatch(1)
+        val disposable3 = it.scheduleDirect {
+            cdl2.countDown()
+        }
+        cdl2.await()
+        expect(9)
+        assertFalse(disposable3.isDisposed)
+        // check that the scheduler automatically disposes of the tasks after being shut down
+        it.shutdown()
+        setScheduler(10, -1)
+        val disposable4 = it.scheduleDirect {
+            expectUnreached()
+        }
+        assertTrue(disposable4.isDisposed)
+        RxJavaPlugins.setScheduleHandler(null)
+        finish(11)
+    }
+
+    @Test
+    fun testSchedulerWithNoDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithNoDelay(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerWithNoDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithNoDelay(scheduler.createWorker()::schedule)
+    }
+
+    private suspend fun testRunnableWithNoDelay(block: RxSchedulerBlockNoDelay) {
+        expect(1)
+        suspendCancellableCoroutine<Unit> {
+            block(Runnable {
+                expect(2)
+                it.resume(Unit)
+            })
+        }
+        yield()
+        finish(3)
+    }
+
+    @Test
+    fun testSchedulerWithDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler::scheduleDirect, 300)
+    }
+
+    @Test
+    fun testSchedulerWorkerWithDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler.createWorker()::schedule, 300)
+    }
+
+    @Test
+    fun testSchedulerWithZeroDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerWithZeroDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler.createWorker()::schedule)
+    }
+
+    private suspend fun testRunnableWithDelay(block: RxSchedulerBlockWithDelay, delayMillis: Long = 0) {
+        expect(1)
+        suspendCancellableCoroutine<Unit> {
+            block({
+                expect(2)
+                it.resume(Unit)
+            }, delayMillis, TimeUnit.MILLISECONDS)
+        }
+        finish(3)
+    }
+
+    @Test
+    fun testAsSchedulerWithNegativeDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler::scheduleDirect, -1)
+    }
+
+    @Test
+    fun testAsSchedulerWorkerWithNegativeDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler.createWorker()::schedule, -1)
+    }
+
+    @Test
+    fun testSchedulerImmediateDispose(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableImmediateDispose(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerImmediateDispose(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableImmediateDispose(scheduler.createWorker()::schedule)
+    }
+
+    private fun testRunnableImmediateDispose(block: RxSchedulerBlockNoDelay) {
+        val disposable = block {
+            expectUnreached()
+        }
+        disposable.dispose()
+    }
+
+    @Test
+    fun testConvertDispatcherToOriginalScheduler(): Unit = runTest {
+        val originalScheduler = Schedulers.io()
+        val dispatcher = originalScheduler.asCoroutineDispatcher()
+        val scheduler = dispatcher.asScheduler()
+        assertSame(originalScheduler, scheduler)
+    }
+
+    @Test
+    fun testConvertSchedulerToOriginalDispatcher(): Unit = runTest {
+        val originalDispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = originalDispatcher.asScheduler()
+        val dispatcher = scheduler.asCoroutineDispatcher()
+        assertSame(originalDispatcher, dispatcher)
+    }
+
+    @Test
+    fun testSchedulerExpectRxPluginsCall(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableExpectRxPluginsCall(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerExpectRxPluginsCall(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableExpectRxPluginsCall(scheduler.createWorker()::schedule)
+    }
+
+    private suspend fun testRunnableExpectRxPluginsCall(block: RxSchedulerBlockNoDelay) {
+        expect(1)
+        setScheduler(2, 4)
+        suspendCancellableCoroutine<Unit> {
+            block(Runnable {
+                expect(5)
+                it.resume(Unit)
+            })
+            expect(3)
+        }
+        RxJavaPlugins.setScheduleHandler(null)
+        finish(6)
+    }
+
+    @Test
+    fun testSchedulerExpectRxPluginsCallWithDelay(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableExpectRxPluginsCallDelay(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerExpectRxPluginsCallWithDelay(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        val worker = scheduler.createWorker()
+        testRunnableExpectRxPluginsCallDelay(worker::schedule)
+    }
+
+    private suspend fun testRunnableExpectRxPluginsCallDelay(block: RxSchedulerBlockWithDelay) {
+        expect(1)
+        setScheduler(2, 4)
+        suspendCancellableCoroutine<Unit> {
+            block({
+                expect(5)
+                it.resume(Unit)
+            }, 10, TimeUnit.MILLISECONDS)
+            expect(3)
+        }
+        RxJavaPlugins.setScheduleHandler(null)
+        finish(6)
+    }
+
+    private fun setScheduler(expectedCountOnSchedule: Int, expectCountOnRun: Int) {
+        RxJavaPlugins.setScheduleHandler {
+            expect(expectedCountOnSchedule)
+            Runnable {
+                expect(expectCountOnRun)
+                it.run()
+            }
+        }
+    }
+
+    /**
+     * Tests that [Scheduler.Worker] runs all work sequentially.
+     */
+    @Test
+    fun testWorkerSequentialOrdering() = runTest {
+        expect(1)
+        val scheduler = Dispatchers.Default.asScheduler()
+        val worker = scheduler.createWorker()
+        val iterations = 100
+        for (i in 0..iterations) {
+            worker.schedule {
+                expect(2 + i)
+            }
+        }
+        suspendCoroutine<Unit> {
+            worker.schedule {
+                it.resume(Unit)
+            }
+        }
+        finish((iterations + 2) + 1)
+    }
+
+    /**
+     * Test that ensures that delays are actually respected (tasks scheduled sooner in the future run before tasks scheduled later,
+     * even when the later task is submitted before the earlier one)
+     */
+    @Test
+    fun testSchedulerRespectsDelays(): Unit = runTest {
+        val scheduler = Dispatchers.Default.asScheduler()
+        testRunnableRespectsDelays(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerRespectsDelays(): Unit = runTest {
+        val scheduler = Dispatchers.Default.asScheduler()
+        testRunnableRespectsDelays(scheduler.createWorker()::schedule)
+    }
+
+    private suspend fun testRunnableRespectsDelays(block: RxSchedulerBlockWithDelay) {
+        expect(1)
+        val semaphore = Semaphore(2, 2)
+        block({
+            expect(3)
+            semaphore.release()
+        }, 100, TimeUnit.MILLISECONDS)
+        block({
+            expect(2)
+            semaphore.release()
+        }, 1, TimeUnit.MILLISECONDS)
+        semaphore.acquire()
+        semaphore.acquire()
+        finish(4)
+    }
+
+    /**
+     * Tests that cancelling a runnable in one worker doesn't affect work in another scheduler.
+     *
+     * This is part of expected behavior documented.
+     */
+    @Test
+    fun testMultipleWorkerCancellation(): Unit = runTest {
+        expect(1)
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        suspendCancellableCoroutine<Unit> {
+            val workerOne = scheduler.createWorker()
+            workerOne.schedule({
+                expect(3)
+                it.resume(Unit)
+            }, 50, TimeUnit.MILLISECONDS)
+            val workerTwo = scheduler.createWorker()
+            workerTwo.schedule({
+                expectUnreached()
+            }, 1000, TimeUnit.MILLISECONDS)
+            workerTwo.dispose()
+            expect(2)
+        }
+        finish(4)
+    }
+}
+
+typealias RxSchedulerBlockNoDelay = (Runnable) -> Disposable
+typealias RxSchedulerBlockWithDelay = (Runnable, Long, TimeUnit) -> Disposable
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx3/README.md b/reactive/kotlinx-coroutines-rx3/README.md
index 1530558..753df27 100644
--- a/reactive/kotlinx-coroutines-rx3/README.md
+++ b/reactive/kotlinx-coroutines-rx3/README.md
@@ -50,40 +50,40 @@
 <!--- 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
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[CoroutineDispatcher]: https://kotlinlang.org/api/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
+[ProducerScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
 
 <!--- INDEX kotlinx.coroutines.flow -->
 
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+[Flow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
 
 <!--- MODULE kotlinx-coroutines-rx3 -->
 <!--- INDEX kotlinx.coroutines.rx3 -->
 
-[rxCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-completable.html
-[rxMaybe]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-maybe.html
-[rxSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-single.html
-[rxObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-observable.html
-[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-flowable.html
-[Flow.asFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-flowable.html
-[Flow.asObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-observable.html
-[ObservableSource.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-flow.html
-[io.reactivex.rxjava3.core.CompletableSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await.html
-[io.reactivex.rxjava3.core.MaybeSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single.html
-[io.reactivex.rxjava3.core.MaybeSource.awaitSingleOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single-or-null.html
-[io.reactivex.rxjava3.core.SingleSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-default.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-else.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-null.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single.html
-[kotlinx.coroutines.Job.asCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-completable.html
-[kotlinx.coroutines.Deferred.asSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-single.html
-[io.reactivex.rxjava3.core.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-coroutine-dispatcher.html
+[rxCompletable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-completable.html
+[rxMaybe]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-maybe.html
+[rxSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-single.html
+[rxObservable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-observable.html
+[rxFlowable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-flowable.html
+[Flow.asFlowable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-flowable.html
+[Flow.asObservable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-observable.html
+[ObservableSource.asFlow]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-flow.html
+[io.reactivex.rxjava3.core.CompletableSource.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await.html
+[io.reactivex.rxjava3.core.MaybeSource.awaitSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single.html
+[io.reactivex.rxjava3.core.MaybeSource.awaitSingleOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single-or-null.html
+[io.reactivex.rxjava3.core.SingleSource.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitFirst]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrDefault]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-default.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrElse]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-else.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-null.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single.html
+[kotlinx.coroutines.Job.asCompletable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-completable.html
+[kotlinx.coroutines.Deferred.asSingle]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-single.html
+[io.reactivex.rxjava3.core.Scheduler.asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-coroutine-dispatcher.html
 
 <!--- END -->
 
diff --git a/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api b/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api
index f6f3f1d..5776214 100644
--- a/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api
+++ b/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api
@@ -58,7 +58,9 @@
 }
 
 public final class kotlinx/coroutines/rx3/RxSchedulerKt {
-	public static final fun asCoroutineDispatcher (Lio/reactivex/rxjava3/core/Scheduler;)Lkotlinx/coroutines/rx3/SchedulerCoroutineDispatcher;
+	public static final fun asCoroutineDispatcher (Lio/reactivex/rxjava3/core/Scheduler;)Lkotlinx/coroutines/CoroutineDispatcher;
+	public static final synthetic fun asCoroutineDispatcher (Lio/reactivex/rxjava3/core/Scheduler;)Lkotlinx/coroutines/rx3/SchedulerCoroutineDispatcher;
+	public static final fun asScheduler (Lkotlinx/coroutines/CoroutineDispatcher;)Lio/reactivex/rxjava3/core/Scheduler;
 }
 
 public final class kotlinx/coroutines/rx3/RxSingleKt {
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt
index 2a14cf7..754dd79 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt
@@ -81,7 +81,7 @@
  */
 @Deprecated(
     message = "Deprecated in favor of awaitSingleOrNull()",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingleOrNull()")
 ) // Warning since 1.5, error in 1.6, hidden in 1.7
 public suspend fun <T> MaybeSource<T>.await(): T? = awaitSingleOrNull()
@@ -104,7 +104,7 @@
  */
 @Deprecated(
     message = "Deprecated in favor of awaitSingleOrNull()",
-    level = DeprecationLevel.WARNING,
+    level = DeprecationLevel.ERROR,
     replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default")
 ) // Warning since 1.5, error in 1.6, hidden in 1.7
 public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt
index 57007bb..1c5f7c0 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt
@@ -13,6 +13,7 @@
 import kotlinx.coroutines.sync.*
 import kotlin.coroutines.*
 import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.intrinsics.*
 
 /**
  * Creates cold [observable][Observable] that will run a given [block] in a coroutine.
@@ -95,10 +96,22 @@
         element: T,
         block: suspend (SendChannel<T>) -> R
     ) {
-        mutex.onLock.registerSelectClause2(select, null) {
+        val clause =  suspend {
             doLockedNext(element)?.let { throw it }
             block(this)
         }
+
+        // This is the default replacement proposed in onLock replacement
+        launch(start = CoroutineStart.UNDISPATCHED) {
+            mutex.lock()
+            // Already selected -- bail out
+            if (!select.trySelect()) {
+                mutex.unlock()
+                return@launch
+            }
+
+            clause.startCoroutineCancellable(select.completion)
+        }
     }
 
     // assert: mutex.isLocked()
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt b/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt
index 24c3f11..abaf024 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt
@@ -4,16 +4,144 @@
 
 package kotlinx.coroutines.rx3
 
-import io.reactivex.rxjava3.core.Scheduler
+import io.reactivex.rxjava3.core.*
+import io.reactivex.rxjava3.disposables.*
+import io.reactivex.rxjava3.plugins.*
+import kotlinx.atomicfu.*
 import kotlinx.coroutines.*
-import java.util.concurrent.TimeUnit
-import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.channels.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
 
 /**
  * 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)
+public fun Scheduler.asCoroutineDispatcher(): CoroutineDispatcher =
+    if (this is DispatcherScheduler) {
+        dispatcher
+    } else {
+        SchedulerCoroutineDispatcher(this)
+    }
+
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.4.2, binary compatibility with earlier versions")
+@JvmName("asCoroutineDispatcher")
+public fun Scheduler.asCoroutineDispatcher0(): SchedulerCoroutineDispatcher =
+    SchedulerCoroutineDispatcher(this)
+
+/**
+ * Converts an instance of [CoroutineDispatcher] to an implementation of [Scheduler].
+ */
+public fun CoroutineDispatcher.asScheduler(): Scheduler =
+    if (this is SchedulerCoroutineDispatcher) {
+        scheduler
+    } else {
+        DispatcherScheduler(this)
+    }
+
+private class DispatcherScheduler(@JvmField val dispatcher: CoroutineDispatcher) : Scheduler() {
+
+    private val schedulerJob = SupervisorJob()
+
+    /**
+     * The scope for everything happening in this [DispatcherScheduler].
+     *
+     * Running tasks, too, get launched under this scope, because [shutdown] should cancel the running tasks as well.
+     */
+    private val scope = CoroutineScope(schedulerJob + dispatcher)
+
+    /**
+     * The counter of created workers, for their pretty-printing.
+     */
+    private val workerCounter = atomic(1L)
+
+    override fun scheduleDirect(block: Runnable, delay: Long, unit: TimeUnit): Disposable =
+        scope.scheduleTask(block, unit.toMillis(delay)) { task ->
+            Runnable { scope.launch { task() } }
+        }
+
+    override fun createWorker(): Worker = DispatcherWorker(workerCounter.getAndIncrement(), dispatcher, schedulerJob)
+
+    override fun shutdown() {
+        schedulerJob.cancel()
+    }
+
+    private class DispatcherWorker(
+        private val counter: Long,
+        private val dispatcher: CoroutineDispatcher,
+        parentJob: Job
+    ) : Worker() {
+
+        private val workerJob = SupervisorJob(parentJob)
+        private val workerScope = CoroutineScope(workerJob + dispatcher)
+        private val blockChannel = Channel<suspend () -> Unit>(Channel.UNLIMITED)
+
+        init {
+            workerScope.launch {
+                blockChannel.consumeEach {
+                    it()
+                }
+            }
+        }
+
+        override fun schedule(block: Runnable, delay: Long, unit: TimeUnit): Disposable =
+            workerScope.scheduleTask(block, unit.toMillis(delay)) { task ->
+                Runnable { blockChannel.trySend(task) }
+            }
+
+        override fun isDisposed(): Boolean = !workerScope.isActive
+
+        override fun dispose() {
+            blockChannel.close()
+            workerJob.cancel()
+        }
+
+        override fun toString(): String = "$dispatcher (worker $counter, ${if (isDisposed) "disposed" else "active"})"
+    }
+
+    override fun toString(): String = dispatcher.toString()
+}
+
+private typealias Task = suspend () -> Unit
+
+/**
+ * Schedule [block] so that an adapted version of it, wrapped in [adaptForScheduling], executes after [delayMillis]
+ * milliseconds.
+ */
+private fun CoroutineScope.scheduleTask(
+    block: Runnable,
+    delayMillis: Long,
+    adaptForScheduling: (Task) -> Runnable
+): Disposable {
+    val ctx = coroutineContext
+    var handle: DisposableHandle? = null
+    val disposable = Disposable.fromRunnable {
+        // null if delay <= 0
+        handle?.dispose()
+    }
+    val decoratedBlock = RxJavaPlugins.onSchedule(block)
+    suspend fun task() {
+        if (disposable.isDisposed) return
+        try {
+            runInterruptible {
+                decoratedBlock.run()
+            }
+        } catch (e: Throwable) {
+            handleUndeliverableException(e, ctx)
+        }
+    }
+
+    val toSchedule = adaptForScheduling(::task)
+    if (!isActive) return Disposable.disposed()
+    if (delayMillis <= 0) {
+        toSchedule.run()
+    } else {
+        @Suppress("INVISIBLE_MEMBER")
+        ctx.delay.invokeOnTimeout(delayMillis, toSchedule, ctx).let { handle = it }
+    }
+    return disposable
+}
 
 /**
  * Implements [CoroutineDispatcher] on top of an arbitrary [Scheduler].
@@ -45,8 +173,10 @@
 
     /** @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-rx3/test/ObservableMultiTest.kt b/reactive/kotlinx-coroutines-rx3/test/ObservableMultiTest.kt
index b4adf7a..d7c799d 100644
--- a/reactive/kotlinx-coroutines-rx3/test/ObservableMultiTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/ObservableMultiTest.kt
@@ -6,6 +6,7 @@
 
 import io.reactivex.rxjava3.core.*
 import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
 import org.junit.Test
 import java.io.*
 import kotlin.test.*
@@ -34,7 +35,7 @@
             // concurrent emitters (many coroutines)
             val jobs = List(n) {
                 // launch
-                launch {
+                launch(Dispatchers.Default) {
                     val i = it
                     send(i)
                 }
@@ -48,6 +49,29 @@
     }
 
     @Test
+    fun testConcurrentStressOnSend() {
+        val n = 10_000 * stressTestMultiplier
+        val observable = rxObservable<Int> {
+            newCoroutineContext(coroutineContext)
+            // concurrent emitters (many coroutines)
+            val jobs = List(n) {
+                // launch
+                launch(Dispatchers.Default) {
+                    val i = it
+                    select<Unit> {
+                        onSend(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 = rxObservable(Dispatchers.Unconfined) {
diff --git a/reactive/kotlinx-coroutines-rx3/test/SchedulerStressTest.kt b/reactive/kotlinx-coroutines-rx3/test/SchedulerStressTest.kt
new file mode 100644
index 0000000..5abb511
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx3/test/SchedulerStressTest.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.rx3
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.util.concurrent.*
+
+class SchedulerStressTest : TestBase() {
+    @Before
+    fun setup() {
+        ignoreLostThreads("RxCachedThreadScheduler-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+    }
+
+    /**
+     * Test that we don't get an OOM if we schedule many jobs at once.
+     * It's expected that if you don't dispose you'd see an OOM error.
+     */
+    @Test
+    fun testSchedulerDisposed(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableDisposed(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerDisposed(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        val worker = scheduler.createWorker()
+        testRunnableDisposed(worker::schedule)
+    }
+
+    private suspend fun testRunnableDisposed(block: RxSchedulerBlockNoDelay) {
+        val n = 2000 * stressTestMultiplier
+        repeat(n) {
+            val a = ByteArray(1000000) //1MB
+            val disposable = block(Runnable {
+                keepMe(a)
+                expectUnreached()
+            })
+            disposable.dispose()
+            yield() // allow the scheduled task to observe that it was disposed
+        }
+    }
+
+    /**
+     * Test function that holds a reference. Used for testing OOM situations
+     */
+    private fun keepMe(a: ByteArray) {
+        Thread.sleep(a.size / (a.size + 1) + 10L)
+    }
+
+    /**
+     * Test that we don't get an OOM if we schedule many delayed jobs at once. It's expected that if you don't dispose that you'd
+     * see a OOM error.
+     */
+    @Test
+    fun testSchedulerDisposedDuringDelay(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableDisposedDuringDelay(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerDisposedDuringDelay(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        val worker = scheduler.createWorker()
+        testRunnableDisposedDuringDelay(worker::schedule)
+    }
+
+    private fun testRunnableDisposedDuringDelay(block: RxSchedulerBlockWithDelay) {
+        val n = 2000 * stressTestMultiplier
+        repeat(n) {
+            val a = ByteArray(1000000) //1MB
+            val delayMillis: Long = 10
+            val disposable = block(Runnable {
+                keepMe(a)
+                expectUnreached()
+            }, delayMillis, TimeUnit.MILLISECONDS)
+            disposable.dispose()
+        }
+    }
+}
diff --git a/reactive/kotlinx-coroutines-rx3/test/SchedulerTest.kt b/reactive/kotlinx-coroutines-rx3/test/SchedulerTest.kt
index 9e95c21..c966cdf 100644
--- a/reactive/kotlinx-coroutines-rx3/test/SchedulerTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/SchedulerTest.kt
@@ -4,10 +4,18 @@
 
 package kotlinx.coroutines.rx3
 
-import io.reactivex.rxjava3.schedulers.Schedulers
+import io.reactivex.rxjava3.core.*
+import io.reactivex.rxjava3.disposables.*
+import io.reactivex.rxjava3.plugins.*
+import io.reactivex.rxjava3.schedulers.*
 import kotlinx.coroutines.*
-import org.junit.Before
+import kotlinx.coroutines.sync.*
+import org.junit.*
 import org.junit.Test
+import java.lang.Runnable
+import java.util.concurrent.*
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.*
 import kotlin.test.*
 
 class SchedulerTest : TestBase() {
@@ -17,7 +25,7 @@
     }
 
     @Test
-    fun testIoScheduler(): Unit = runBlocking {
+    fun testIoScheduler(): Unit = runTest {
         expect(1)
         val mainThread = Thread.currentThread()
         withContext(Schedulers.io().asCoroutineDispatcher()) {
@@ -31,4 +39,458 @@
         }
         finish(4)
     }
+
+    /** Tests [toString] implementations of [CoroutineDispatcher.asScheduler] and its [Scheduler.Worker]. */
+    @Test
+    fun testSchedulerToString() {
+        val name = "Dispatchers.Default"
+        val scheduler = Dispatchers.Default.asScheduler()
+        assertContains(scheduler.toString(), name)
+        val worker = scheduler.createWorker()
+        val activeWorkerName = worker.toString()
+        assertContains(worker.toString(), name)
+        worker.dispose()
+        val disposedWorkerName = worker.toString()
+        assertNotEquals(activeWorkerName, disposedWorkerName)
+    }
+
+    private fun runSchedulerTest(nThreads: Int = 1, action: (Scheduler) -> Unit) {
+        val future = CompletableFuture<Unit>()
+        try {
+            newFixedThreadPoolContext(nThreads, "test").use { dispatcher ->
+                RxJavaPlugins.setErrorHandler {
+                    if (!future.completeExceptionally(it)) {
+                        handleUndeliverableException(it, dispatcher)
+                    }
+                }
+                action(dispatcher.asScheduler())
+            }
+        } finally {
+            RxJavaPlugins.setErrorHandler(null)
+        }
+        future.complete(Unit)
+        future.getNow(Unit) // rethrow any encountered errors
+    }
+
+    private fun ensureSeparateThread(schedule: (Runnable, Long, TimeUnit) -> Unit, scheduleNoDelay: (Runnable) -> Unit) {
+        val mainThread = Thread.currentThread()
+        val cdl1 = CountDownLatch(1)
+        val cdl2 = CountDownLatch(1)
+        expect(1)
+        val thread = AtomicReference<Thread?>(null)
+        fun checkThread() {
+            val current = Thread.currentThread()
+            thread.getAndSet(current)?.let { assertEquals(it, current) }
+        }
+        schedule({
+            assertNotSame(mainThread, Thread.currentThread())
+            checkThread()
+            cdl2.countDown()
+        }, 300, TimeUnit.MILLISECONDS)
+        scheduleNoDelay {
+            expect(2)
+            checkThread()
+            assertNotSame(mainThread, Thread.currentThread())
+            cdl1.countDown()
+        }
+        cdl1.await()
+        cdl2.await()
+        finish(3)
+    }
+
+    /**
+     * Tests [Scheduler.scheduleDirect] for [CoroutineDispatcher.asScheduler] on a single-threaded dispatcher.
+     */
+    @Test
+    fun testSingleThreadedDispatcherDirect(): Unit = runSchedulerTest(1) {
+        ensureSeparateThread(it::scheduleDirect, it::scheduleDirect)
+    }
+
+    /**
+     * Tests [Scheduler.Worker.schedule] for [CoroutineDispatcher.asScheduler] running its tasks on the correct thread.
+     */
+    @Test
+    fun testSingleThreadedWorker(): Unit = runSchedulerTest(1) {
+        val worker = it.createWorker()
+        ensureSeparateThread(worker::schedule, worker::schedule)
+    }
+
+    private fun checkCancelling(schedule: (Runnable, Long, TimeUnit) -> Disposable) {
+        // cancel the task before it has a chance to run.
+        val handle1 = schedule({
+            throw IllegalStateException("should have been successfully cancelled")
+        }, 10_000, TimeUnit.MILLISECONDS)
+        handle1.dispose()
+        // cancel the task after it started running.
+        val cdl1 = CountDownLatch(1)
+        val cdl2 = CountDownLatch(1)
+        val handle2 = schedule({
+            cdl1.countDown()
+            cdl2.await()
+            if (Thread.interrupted())
+                throw IllegalStateException("cancelling the task should not interrupt the thread")
+        }, 100, TimeUnit.MILLISECONDS)
+        cdl1.await()
+        handle2.dispose()
+        cdl2.countDown()
+    }
+
+    /**
+     * Test cancelling [Scheduler.scheduleDirect] for [CoroutineDispatcher.asScheduler].
+     */
+    @Test
+    fun testCancellingDirect(): Unit = runSchedulerTest {
+        checkCancelling(it::scheduleDirect)
+    }
+
+    /**
+     * Test cancelling [Scheduler.Worker.schedule] for [CoroutineDispatcher.asScheduler].
+     */
+    @Test
+    fun testCancellingWorker(): Unit = runSchedulerTest {
+        val worker = it.createWorker()
+        checkCancelling(worker::schedule)
+    }
+
+    /**
+     * Test shutting down [CoroutineDispatcher.asScheduler].
+     */
+    @Test
+    fun testShuttingDown() {
+        val n = 5
+        runSchedulerTest(nThreads = n) { scheduler ->
+            val cdl1 = CountDownLatch(n)
+            val cdl2 = CountDownLatch(1)
+            val cdl3 = CountDownLatch(n)
+            repeat(n) {
+                scheduler.scheduleDirect {
+                    cdl1.countDown()
+                    try {
+                        cdl2.await()
+                    } catch (e: InterruptedException) {
+                        // this is the expected outcome
+                        cdl3.countDown()
+                    }
+                }
+            }
+            cdl1.await()
+            scheduler.shutdown()
+            if (!cdl3.await(1, TimeUnit.SECONDS)) {
+                cdl2.countDown()
+                error("the tasks were not cancelled when the scheduler was shut down")
+            }
+        }
+    }
+
+    /** Tests that there are no uncaught exceptions if [Disposable.dispose] on a worker happens when tasks are present. */
+    @Test
+    fun testDisposingWorker() = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        val worker = scheduler.createWorker()
+        yield() // so that the worker starts waiting on the channel
+        assertFalse(worker.isDisposed)
+        worker.dispose()
+        assertTrue(worker.isDisposed)
+    }
+
+    /** Tests trying to use a [Scheduler.Worker]/[Scheduler] after [Scheduler.Worker.dispose]/[Scheduler.shutdown]. */
+    @Test
+    fun testSchedulingAfterDisposing() = runSchedulerTest {
+        expect(1)
+        val worker = it.createWorker()
+        // use CDL to ensure that the worker has properly initialized
+        val cdl1 = CountDownLatch(1)
+        setScheduler(2, 3)
+        val disposable1 = worker.schedule {
+            cdl1.countDown()
+        }
+        cdl1.await()
+        expect(4)
+        assertFalse(disposable1.isDisposed)
+        setScheduler(6, -1)
+        // check that the worker automatically disposes of the tasks after being disposed
+        assertFalse(worker.isDisposed)
+        worker.dispose()
+        assertTrue(worker.isDisposed)
+        expect(5)
+        val disposable2 = worker.schedule {
+            expectUnreached()
+        }
+        assertTrue(disposable2.isDisposed)
+        setScheduler(7, 8)
+        // ensure that the scheduler still works
+        val cdl2 = CountDownLatch(1)
+        val disposable3 = it.scheduleDirect {
+            cdl2.countDown()
+        }
+        cdl2.await()
+        expect(9)
+        assertFalse(disposable3.isDisposed)
+        // check that the scheduler automatically disposes of the tasks after being shut down
+        it.shutdown()
+        setScheduler(10, -1)
+        val disposable4 = it.scheduleDirect {
+            expectUnreached()
+        }
+        assertTrue(disposable4.isDisposed)
+        RxJavaPlugins.setScheduleHandler(null)
+        finish(11)
+    }
+
+    @Test
+    fun testSchedulerWithNoDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithNoDelay(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerWithNoDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithNoDelay(scheduler.createWorker()::schedule)
+    }
+
+    private suspend fun testRunnableWithNoDelay(block: RxSchedulerBlockNoDelay) {
+        expect(1)
+        suspendCancellableCoroutine<Unit> {
+            block(Runnable {
+                expect(2)
+                it.resume(Unit)
+            })
+        }
+        yield()
+        finish(3)
+    }
+
+    @Test
+    fun testSchedulerWithDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler::scheduleDirect, 300)
+    }
+
+    @Test
+    fun testSchedulerWorkerWithDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler.createWorker()::schedule, 300)
+    }
+
+    @Test
+    fun testSchedulerWithZeroDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerWithZeroDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler.createWorker()::schedule)
+    }
+
+    private suspend fun testRunnableWithDelay(block: RxSchedulerBlockWithDelay, delayMillis: Long = 0) {
+        expect(1)
+        suspendCancellableCoroutine<Unit> {
+            block({
+                expect(2)
+                it.resume(Unit)
+            }, delayMillis, TimeUnit.MILLISECONDS)
+        }
+        finish(3)
+    }
+
+    @Test
+    fun testAsSchedulerWithNegativeDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler::scheduleDirect, -1)
+    }
+
+    @Test
+    fun testAsSchedulerWorkerWithNegativeDelay(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableWithDelay(scheduler.createWorker()::schedule, -1)
+    }
+
+    @Test
+    fun testSchedulerImmediateDispose(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableImmediateDispose(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerImmediateDispose(): Unit = runTest {
+        val scheduler = (currentDispatcher() as CoroutineDispatcher).asScheduler()
+        testRunnableImmediateDispose(scheduler.createWorker()::schedule)
+    }
+
+    private fun testRunnableImmediateDispose(block: RxSchedulerBlockNoDelay) {
+        val disposable = block {
+            expectUnreached()
+        }
+        disposable.dispose()
+    }
+
+    @Test
+    fun testConvertDispatcherToOriginalScheduler(): Unit = runTest {
+        val originalScheduler = Schedulers.io()
+        val dispatcher = originalScheduler.asCoroutineDispatcher()
+        val scheduler = dispatcher.asScheduler()
+        assertSame(originalScheduler, scheduler)
+    }
+
+    @Test
+    fun testConvertSchedulerToOriginalDispatcher(): Unit = runTest {
+        val originalDispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = originalDispatcher.asScheduler()
+        val dispatcher = scheduler.asCoroutineDispatcher()
+        assertSame(originalDispatcher, dispatcher)
+    }
+
+    @Test
+    fun testSchedulerExpectRxPluginsCall(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableExpectRxPluginsCall(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerExpectRxPluginsCall(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableExpectRxPluginsCall(scheduler.createWorker()::schedule)
+    }
+
+    private suspend fun testRunnableExpectRxPluginsCall(block: RxSchedulerBlockNoDelay) {
+        expect(1)
+        setScheduler(2, 4)
+        suspendCancellableCoroutine<Unit> {
+            block(Runnable {
+                expect(5)
+                it.resume(Unit)
+            })
+            expect(3)
+        }
+        RxJavaPlugins.setScheduleHandler(null)
+        finish(6)
+    }
+
+    @Test
+    fun testSchedulerExpectRxPluginsCallWithDelay(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        testRunnableExpectRxPluginsCallDelay(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerExpectRxPluginsCallWithDelay(): Unit = runTest {
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        val worker = scheduler.createWorker()
+        testRunnableExpectRxPluginsCallDelay(worker::schedule)
+    }
+
+    private suspend fun testRunnableExpectRxPluginsCallDelay(block: RxSchedulerBlockWithDelay) {
+        expect(1)
+        setScheduler(2, 4)
+        suspendCancellableCoroutine<Unit> {
+            block({
+                expect(5)
+                it.resume(Unit)
+            }, 10, TimeUnit.MILLISECONDS)
+            expect(3)
+        }
+        RxJavaPlugins.setScheduleHandler(null)
+        finish(6)
+    }
+
+    private fun setScheduler(expectedCountOnSchedule: Int, expectCountOnRun: Int) {
+        RxJavaPlugins.setScheduleHandler {
+            expect(expectedCountOnSchedule)
+            Runnable {
+                expect(expectCountOnRun)
+                it.run()
+            }
+        }
+    }
+
+    /**
+     * Tests that [Scheduler.Worker] runs all work sequentially.
+     */
+    @Test
+    fun testWorkerSequentialOrdering() = runTest {
+        expect(1)
+        val scheduler = Dispatchers.Default.asScheduler()
+        val worker = scheduler.createWorker()
+        val iterations = 100
+        for (i in 0..iterations) {
+            worker.schedule {
+                expect(2 + i)
+            }
+        }
+        suspendCoroutine<Unit> {
+            worker.schedule {
+                it.resume(Unit)
+            }
+        }
+        finish((iterations + 2) + 1)
+    }
+
+    /**
+     * Test that ensures that delays are actually respected (tasks scheduled sooner in the future run before tasks scheduled later,
+     * even when the later task is submitted before the earlier one)
+     */
+    @Test
+    fun testSchedulerRespectsDelays(): Unit = runTest {
+        val scheduler = Dispatchers.Default.asScheduler()
+        testRunnableRespectsDelays(scheduler::scheduleDirect)
+    }
+
+    @Test
+    fun testSchedulerWorkerRespectsDelays(): Unit = runTest {
+        val scheduler = Dispatchers.Default.asScheduler()
+        testRunnableRespectsDelays(scheduler.createWorker()::schedule)
+    }
+
+    private suspend fun testRunnableRespectsDelays(block: RxSchedulerBlockWithDelay) {
+        expect(1)
+        val semaphore = Semaphore(2, 2)
+        block({
+            expect(3)
+            semaphore.release()
+        }, 100, TimeUnit.MILLISECONDS)
+        block({
+            expect(2)
+            semaphore.release()
+        }, 1, TimeUnit.MILLISECONDS)
+        semaphore.acquire()
+        semaphore.acquire()
+        finish(4)
+    }
+
+    /**
+     * Tests that cancelling a runnable in one worker doesn't affect work in another scheduler.
+     *
+     * This is part of expected behavior documented.
+     */
+    @Test
+    fun testMultipleWorkerCancellation(): Unit = runTest {
+        expect(1)
+        val dispatcher = currentDispatcher() as CoroutineDispatcher
+        val scheduler = dispatcher.asScheduler()
+        suspendCancellableCoroutine<Unit> {
+            val workerOne = scheduler.createWorker()
+            workerOne.schedule({
+                expect(3)
+                it.resume(Unit)
+            }, 50, TimeUnit.MILLISECONDS)
+            val workerTwo = scheduler.createWorker()
+            workerTwo.schedule({
+                expectUnreached()
+            }, 1000, TimeUnit.MILLISECONDS)
+            workerTwo.dispose()
+            expect(2)
+        }
+        finish(4)
+    }
 }
+
+typealias RxSchedulerBlockNoDelay = (Runnable) -> Disposable
+typealias RxSchedulerBlockWithDelay = (Runnable, Long, TimeUnit) -> Disposable
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 44effa7..f0a7648 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,7 +8,7 @@
 
         // JMH
         id "net.ltgt.apt" version "0.21"
-        id "me.champeau.gradle.jmh" version "0.5.2"
+        id "me.champeau.gradle.jmh" version "0.5.3"
     }
 
     repositories {
@@ -18,7 +18,6 @@
 }
 
 rootProject.name = 'kotlinx.coroutines'
-enableFeaturePreview('GRADLE_METADATA')
 
 def module(String path) {
     int i = path.lastIndexOf('/')
@@ -59,5 +58,3 @@
 if (!build_snapshot_train) {
     module('js/example-frontend-js')
 }
-
-module('integration-testing')
diff --git a/site/deploy.sh b/site/deploy.sh
deleted file mode 100755
index a04e492..0000000
--- a/site/deploy.sh
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env bash
-
-#
-# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
-#
-
-# Abort on first error
-set -e
-
-# Directories
-SITE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-ROOT_DIR="$SITE_DIR/.."
-BUILD_DIR="$ROOT_DIR/build"
-DIST_DIR="$BUILD_DIR/dokka/htmlMultiModule"
-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 dokkaHtmlMultiModule
-
-# 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
-
-# Remove all the old documentation
-git rm -r * > /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/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md
index 71b2d69..4ee898e 100644
--- a/ui/coroutines-guide-ui.md
+++ b/ui/coroutines-guide-ui.md
@@ -110,7 +110,7 @@
 `app/build.gradle` file:
 
 ```groovy
-implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
+implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
 ```
 
 You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your 
@@ -608,30 +608,30 @@
 <!--- 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/cancel.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-[CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html
-[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[CoroutineStart.UNDISPATCHED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-u-n-d-i-s-p-a-t-c-h-e-d/index.html
+[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Job.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
+[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[MainScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
+[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[CoroutineStart]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html
+[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[CoroutineStart.UNDISPATCHED]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-u-n-d-i-s-p-a-t-c-h-e-d/index.html
 
 <!--- INDEX kotlinx.coroutines.channels -->
 
-[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
-[SendChannel.trySend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
-[SendChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html
-[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
-[Channel.Factory.CONFLATED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/-factory/-c-o-n-f-l-a-t-e-d.html
+[actor]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
+[SendChannel.trySend]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
+[SendChannel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html
+[Channel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+[Channel.Factory.CONFLATED]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/-factory/-c-o-n-f-l-a-t-e-d.html
 
 <!--- MODULE kotlinx-coroutines-javafx -->
 <!--- INDEX kotlinx.coroutines.javafx -->
 
-[kotlinx.coroutines.Dispatchers.JavaFx]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-javafx/kotlinx.coroutines.javafx/-java-fx.html
+[kotlinx.coroutines.Dispatchers.JavaFx]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-javafx/kotlinx.coroutines.javafx/-java-fx.html
 
 <!--- MODULE kotlinx-coroutines-android -->
 <!--- INDEX kotlinx.coroutines.android -->
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts
index 2fd0b81..625ce72 100644
--- a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts
@@ -2,10 +2,17 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
+project.configureAar()
+
 dependencies {
-    kotlinCompilerPluginClasspathMain(project(":kotlinx-coroutines-core"))
+    configureAarUnpacking()
+
     testImplementation("com.google.android:android:${version("android")}")
     testImplementation("org.robolectric:robolectric:${version("robolectric")}")
+    // Required by robolectric
+    testImplementation("androidx.test:core:1.2.0")
+    testImplementation("androidx.test:monitor:1.2.0")
+
     testImplementation(project(":kotlinx-coroutines-test"))
     testImplementation(project(":kotlinx-coroutines-android"))
 }
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
index bcc12d5..676ee43 100644
--- 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
@@ -26,6 +26,7 @@
 
 @Config(manifest = Config.NONE, sdk = [28])
 @RunWith(InitMainDispatcherBeforeRobolectricTestRunner::class)
+@LooperMode(LooperMode.Mode.LEGACY)
 class CustomizedRobolectricTest : TestBase() {
     @Test
     fun testComponent()  {
@@ -52,4 +53,4 @@
         mainLooper.unPause()
         assertTrue(component.launchCompleted)
     }
-}
\ No newline at end of file
+}
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
index eab6fc1..99744f8 100644
--- 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
@@ -15,6 +15,7 @@
 
 @RunWith(RobolectricTestRunner::class)
 @Config(manifest = Config.NONE, sdk = [28])
+@LooperMode(LooperMode.Mode.LEGACY)
 open class FirstRobolectricTest {
     @Test
     fun testComponent()  {
diff --git a/ui/kotlinx-coroutines-android/build.gradle.kts b/ui/kotlinx-coroutines-android/build.gradle.kts
index 9cec1dc..7618c52 100644
--- a/ui/kotlinx-coroutines-android/build.gradle.kts
+++ b/ui/kotlinx-coroutines-android/build.gradle.kts
@@ -2,22 +2,32 @@
  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
+import kotlinx.kover.api.*
+
 configurations {
     create("r8")
 }
 
 repositories {
     mavenCentral()
-    jcenter() // https://youtrack.jetbrains.com/issue/IDEA-261387
 }
+
+project.configureAar()
+
 dependencies {
+    configureAarUnpacking()
+
     compileOnly("com.google.android:android:${version("android")}")
     compileOnly("androidx.annotation:annotation:${version("androidx_annotation")}")
 
     testImplementation("com.google.android:android:${version("android")}")
     testImplementation("org.robolectric:robolectric:${version("robolectric")}")
-    testImplementation("org.smali:baksmali:${version("baksmali")}")
+    // Required by robolectric
+    testImplementation("androidx.test:core:1.2.0")
+    testImplementation("androidx.test:monitor:1.2.0")
 
+
+    testImplementation("org.smali:baksmali:${version("baksmali")}")
     "r8"("com.android.tools.build:builder:7.1.0-alpha01")
 }
 
@@ -103,3 +113,8 @@
     }
 }
 
+tasks.withType<Test> {
+    extensions.configure<KoverTaskExtension> {
+        excludes = excludes + listOf("com.android.*", "android.*") // Exclude robolectric-generated classes
+    }
+}
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
index c7cd15f..ef42483 100644
--- 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
@@ -1,5 +1,6 @@
 # When editing this file, update the following files as well:
-# - META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro
+# - META-INF/com.android.tools/r8-upto-3.0.0/coroutines.pro
 # - META-INF/proguard/coroutines.pro
 
 -keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
+-keep class kotlinx.coroutines.android.AndroidExceptionPreHandler {*;}
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
index 0d04990..cf317c4 100644
--- 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
@@ -9,8 +9,6 @@
     boolean ANDROID_DETECTED return true;
 }
 
--keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
-
 # Disable support for "Missing Main Dispatcher", since we always have Android main dispatcher
 -assumenosideeffects class kotlinx.coroutines.internal.MainDispatchersKt {
     boolean SUPPORT_MISSING return false;
@@ -21,4 +19,4 @@
     boolean getASSERTIONS_ENABLED() return false;
     boolean getDEBUG() return false;
     boolean getRECOVER_STACK_TRACES() 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
deleted file mode 100644
index 549d0e8..0000000
--- a/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-upto-1.6.0/coroutines.pro
+++ /dev/null
@@ -1,9 +0,0 @@
-# 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 {*;}
-
--assumenosideeffects class kotlinx.coroutines.internal.FastServiceLoader {
-    boolean ANDROID_DETECTED return true;
-}
\ No newline at end of file
diff --git a/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-upto-3.0.0/coroutines.pro b/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-upto-3.0.0/coroutines.pro
new file mode 100644
index 0000000..1aa2b11
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/resources/META-INF/com.android.tools/r8-upto-3.0.0/coroutines.pro
@@ -0,0 +1,10 @@
+# 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/proguard/coroutines.pro
+
+# After R8 3.0.0 (or probably sometime before that), R8 learned how to optimize
+# classes mentioned in META-INF/services files, and explicitly -keeping them
+# disables these optimizations.
+# https://github.com/Kotlin/kotlinx.coroutines/issues/3111
+-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
+-keep class kotlinx.coroutines.android.AndroidExceptionPreHandler {*;}
diff --git a/ui/kotlinx-coroutines-android/resources/META-INF/proguard/coroutines.pro b/ui/kotlinx-coroutines-android/resources/META-INF/proguard/coroutines.pro
index 6c918d4..087f1ce 100644
--- a/ui/kotlinx-coroutines-android/resources/META-INF/proguard/coroutines.pro
+++ b/ui/kotlinx-coroutines-android/resources/META-INF/proguard/coroutines.pro
@@ -2,6 +2,7 @@
 
 # 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
+# - META-INF/com.android.tools/r8-upto-3.0.0/coroutines.pro
 
 -keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
+-keep class kotlinx.coroutines.android.AndroidExceptionPreHandler {*;}
diff --git a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt
index 7d06752..0bc603e 100644
--- a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt
+++ b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt
@@ -5,12 +5,10 @@
 package kotlinx.coroutines.android
 
 import android.os.*
-import androidx.annotation.*
 import kotlinx.coroutines.*
 import java.lang.reflect.*
 import kotlin.coroutines.*
 
-@Keep
 internal class AndroidExceptionPreHandler :
     AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler
 {
@@ -34,20 +32,21 @@
 
     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.
+         * Android Oreo introduced private API for a global pre-handler for uncaught exceptions, to ensure that the
+         * exceptions are logged even if the default uncaught exception handler is replaced by the app. The pre-handler
+         * is invoked from the Thread's private dispatchUncaughtException() method, so our manual invocation of the
+         * Thread's uncaught exception handler bypasses the pre-handler in Android Oreo, and uncaught coroutine
+         * exceptions are not logged. This issue was addressed in Android Pie, which added a check in the default
+         * uncaught exception handler to invoke the pre-handler if it was not invoked already (see
+         * https://android-review.googlesource.com/c/platform/frameworks/base/+/654578/). So the issue is present only
+         * in Android Oreo.
          *
-         * 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/
+         * We're fixing this by manually invoking the pre-handler using reflection, if running on an Android Oreo SDK
+         * version (26 and 27).
          */
-        val thread = Thread.currentThread()
-        if (Build.VERSION.SDK_INT >= 28) {
-            thread.uncaughtExceptionHandler.uncaughtException(thread, exception)
-        } else {
+        if (Build.VERSION.SDK_INT in 26..27) {
             (preHandler()?.invoke(null) as? Thread.UncaughtExceptionHandler)
-                ?.uncaughtException(thread, exception)
+                ?.uncaughtException(Thread.currentThread(), exception)
         }
     }
 }
diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
index ca8dd0d..5e33169 100644
--- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
+++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
@@ -51,8 +51,10 @@
 
 internal class AndroidDispatcherFactory : MainDispatcherFactory {
 
-    override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
-        HandlerContext(Looper.getMainLooper().asHandler(async = true))
+    override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
+        val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available")
+        return HandlerContext(mainLooper.asHandler(async = true))
+    }
 
     override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
 
@@ -188,7 +190,7 @@
             postFrameCallback(choreographer, cont)
         }
     }
-    // post into looper thread thread to figure it out
+    // post into looper thread to figure it out
     return suspendCancellableCoroutine { cont ->
         Dispatchers.Main.dispatch(EmptyCoroutineContext, Runnable {
             updateChoreographerAndPostFrameCallback(cont)
diff --git a/ui/kotlinx-coroutines-android/test/AndroidExceptionPreHandlerTest.kt b/ui/kotlinx-coroutines-android/test/AndroidExceptionPreHandlerTest.kt
new file mode 100644
index 0000000..1220797
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/test/AndroidExceptionPreHandlerTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.android
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import org.junit.runner.*
+import org.robolectric.*
+import org.robolectric.annotation.*
+import kotlin.test.*
+
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest = Config.NONE, sdk = [27])
+@LooperMode(LooperMode.Mode.LEGACY)
+class AndroidExceptionPreHandlerTest : TestBase() {
+    @Test
+    fun testUnhandledException() = runTest {
+        val previousHandler = Thread.getDefaultUncaughtExceptionHandler()
+        try {
+            Thread.setDefaultUncaughtExceptionHandler { _, e ->
+                expect(3)
+                assertIs<TestException>(e)
+            }
+            expect(1)
+            GlobalScope.launch(Dispatchers.Main) {
+                expect(2)
+                throw TestException()
+            }.join()
+            finish(4)
+        } finally {
+            Thread.setDefaultUncaughtExceptionHandler(previousHandler)
+        }
+    }
+}
diff --git a/ui/kotlinx-coroutines-android/test/DisabledHandlerTest.kt b/ui/kotlinx-coroutines-android/test/DisabledHandlerTest.kt
index a1f0a03..a5b5ec9 100644
--- a/ui/kotlinx-coroutines-android/test/DisabledHandlerTest.kt
+++ b/ui/kotlinx-coroutines-android/test/DisabledHandlerTest.kt
@@ -13,6 +13,7 @@
 
 @RunWith(RobolectricTestRunner::class)
 @Config(manifest = Config.NONE, sdk = [28])
+@LooperMode(LooperMode.Mode.LEGACY)
 class DisabledHandlerTest : TestBase() {
 
     private var delegateToSuper = false
diff --git a/ui/kotlinx-coroutines-android/test/HandlerDispatcherAsyncTest.kt b/ui/kotlinx-coroutines-android/test/HandlerDispatcherAsyncTest.kt
new file mode 100644
index 0000000..c2091f3
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/test/HandlerDispatcherAsyncTest.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.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 java.util.concurrent.*
+import kotlin.test.*
+
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest = Config.NONE, sdk = [28])
+@LooperMode(LooperMode.Mode.LEGACY)
+class HandlerDispatcherAsyncTest : 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 = shadowOf(Looper.getMainLooper())
+        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 = shadowOf(Looper.getMainLooper())
+        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 = shadowOf(Looper.getMainLooper())
+        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 = shadowOf(Looper.getMainLooper())
+        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 = shadowOf(Looper.getMainLooper())
+        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/HandlerDispatcherTest.kt b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt
index 5128a74..fe97ae8 100644
--- a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt
+++ b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
 
 package kotlinx.coroutines.android
@@ -9,139 +9,15 @@
 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 java.util.concurrent.*
 import kotlin.test.*
 
 @RunWith(RobolectricTestRunner::class)
 @Config(manifest = Config.NONE, sdk = [28])
+@LooperMode(LooperMode.Mode.LEGACY)
 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 = shadowOf(Looper.getMainLooper())
-        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 = shadowOf(Looper.getMainLooper())
-        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 = shadowOf(Looper.getMainLooper())
-        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 = shadowOf(Looper.getMainLooper())
-        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 = shadowOf(Looper.getMainLooper())
-        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
-
     @Test
     fun testImmediateDispatcherYield() = runBlocking(Dispatchers.Main) {
         expect(1)
@@ -161,4 +37,103 @@
         assertEquals("Dispatchers.Main", Dispatchers.Main.toString())
         assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString())
     }
+
+    @Test
+    fun testDefaultDelayIsNotDelegatedToMain() = runTest {
+        val mainLooper = Shadows.shadowOf(Looper.getMainLooper())
+        mainLooper.pause()
+        assertFalse { mainLooper.scheduler.areAnyRunnable() }
+
+        val job = launch(Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) {
+            expect(1)
+            delay(Long.MAX_VALUE)
+            expectUnreached()
+        }
+        expect(2)
+        assertEquals(0, mainLooper.scheduler.size())
+        job.cancelAndJoin()
+        finish(3)
+    }
+
+    @Test
+    fun testWithTimeoutIsDelegatedToMain() = runTest {
+        val mainLooper = Shadows.shadowOf(Looper.getMainLooper())
+        mainLooper.pause()
+        assertFalse { mainLooper.scheduler.areAnyRunnable() }
+        val job = launch(Dispatchers.Main, start = CoroutineStart.UNDISPATCHED) {
+            withTimeout(1) {
+                expect(1)
+                hang { expect(3) }
+            }
+            expectUnreached()
+        }
+        expect(2)
+        assertEquals(1, mainLooper.scheduler.size())
+        // Schedule cancellation
+        mainLooper.runToEndOfTasks()
+        job.join()
+        finish(4)
+    }
+
+    @Test
+    fun testDelayDelegatedToMain() = runTest {
+        val mainLooper = Shadows.shadowOf(Looper.getMainLooper())
+        mainLooper.pause()
+        val job = launch(Dispatchers.Main, start = CoroutineStart.UNDISPATCHED) {
+            expect(1)
+            delay(1)
+            expect(3)
+        }
+        expect(2)
+        assertEquals(1, mainLooper.scheduler.size())
+        // Schedule cancellation
+        mainLooper.runToEndOfTasks()
+        job.join()
+        finish(4)
+    }
+
+    @Test
+    fun testAwaitFrame() = runTest {
+        doTestAwaitFrame()
+
+        reset()
+
+        // Now the second test: we cannot test it separately because we're caching choreographer in HandlerDispatcher
+        doTestAwaitWithDetectedChoreographer()
+    }
+
+    private fun CoroutineScope.doTestAwaitFrame() {
+        ShadowChoreographer.setPostFrameCallbackDelay(100)
+        val mainLooper = Shadows.shadowOf(Looper.getMainLooper())
+        mainLooper.pause()
+        launch(Dispatchers.Main, start = CoroutineStart.UNDISPATCHED) {
+            expect(1)
+            awaitFrame()
+            expect(5)
+        }
+        expect(2)
+        // Run choreographer detection
+        mainLooper.runOneTask()
+        expect(3)
+        mainLooper.scheduler.advanceBy(50, TimeUnit.MILLISECONDS)
+        expect(4)
+        mainLooper.scheduler.advanceBy(51, TimeUnit.MILLISECONDS)
+        finish(6)
+    }
+
+    private fun CoroutineScope.doTestAwaitWithDetectedChoreographer() {
+        ShadowChoreographer.setPostFrameCallbackDelay(100)
+        val mainLooper = Shadows.shadowOf(Looper.getMainLooper())
+        launch(Dispatchers.Main, start = CoroutineStart.UNDISPATCHED) {
+            expect(1)
+            awaitFrame()
+            expect(4)
+        }
+        // Run choreographer detection
+        expect(2)
+        mainLooper.scheduler.advanceBy(50, TimeUnit.MILLISECONDS)
+        expect(3)
+        mainLooper.scheduler.advanceBy(51, TimeUnit.MILLISECONDS)
+        finish(5)
+    }
 }
diff --git a/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt b/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt
index 5d60d64..47beb85 100644
--- a/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt
+++ b/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt
@@ -11,7 +11,6 @@
 import java.util.stream.*
 import kotlin.test.*
 
-@Ignore
 class R8ServiceLoaderOptimizationTest : TestBase() {
     private val r8Dex = File(System.getProperty("dexPath")!!).asDexFile()
     private val r8DexNoOptim = File(System.getProperty("noOptimDexPath")!!).asDexFile()
diff --git a/ui/kotlinx-coroutines-javafx/build.gradle.kts b/ui/kotlinx-coroutines-javafx/build.gradle.kts
index 9e30590..f9f6624 100644
--- a/ui/kotlinx-coroutines-javafx/build.gradle.kts
+++ b/ui/kotlinx-coroutines-javafx/build.gradle.kts
@@ -6,17 +6,20 @@
     id("org.openjfx.javafxplugin") version "0.0.9"
 }
 
+configurations {
+    register("javafx")
+    named("compileOnly") {
+        extendsFrom(configurations["javafx"])
+    }
+    named("testImplementation") {
+        extendsFrom(configurations["javafx"])
+    }
+}
+
 javafx {
     version = version("javafx")
     modules = listOf("javafx.controls")
-    configuration = "compileOnly"
-}
-
-sourceSets {
-    test.configure {
-        compileClasspath += configurations.compileOnly
-        runtimeClasspath += configurations.compileOnly
-    }
+    configuration = "javafx"
 }
 
 val JDK_18: String? by lazy {
diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
index 0a35cbf..d158fb7 100644
--- a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
+++ b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
@@ -10,7 +10,6 @@
 import javafx.util.*
 import kotlinx.coroutines.*
 import kotlinx.coroutines.internal.*
-import kotlinx.coroutines.javafx.JavaFx.delay
 import java.lang.UnsupportedOperationException
 import java.lang.reflect.*
 import java.util.concurrent.*
@@ -35,22 +34,18 @@
 
     /** @suppress */
     override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
-        val timeline = schedule(timeMillis, TimeUnit.MILLISECONDS, EventHandler {
+        val timeline = schedule(timeMillis, TimeUnit.MILLISECONDS) {
             with(continuation) { resumeUndispatched(Unit) }
-        })
+        }
         continuation.invokeOnCancellation { timeline.stop() }
     }
 
     /** @suppress */
     override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
-        val timeline = schedule(timeMillis, TimeUnit.MILLISECONDS, EventHandler {
+        val timeline = schedule(timeMillis, TimeUnit.MILLISECONDS) {
             block.run()
-        })
-        return object : DisposableHandle {
-            override fun dispose() {
-                timeline.stop()
-            }
         }
+        return DisposableHandle { timeline.stop() }
     }
 
     private fun schedule(time: Long, unit: TimeUnit, handler: EventHandler<ActionEvent>): Timeline =
diff --git a/ui/kotlinx-coroutines-swing/build.gradle.kts b/ui/kotlinx-coroutines-swing/build.gradle.kts
index b8ca906..157ce40 100644
--- a/ui/kotlinx-coroutines-swing/build.gradle.kts
+++ b/ui/kotlinx-coroutines-swing/build.gradle.kts
@@ -3,5 +3,5 @@
  */
 
 dependencies {
-    testCompile(project(":kotlinx-coroutines-jdk8"))
+    testImplementation(project(":kotlinx-coroutines-jdk8"))
 }
diff --git a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
index d2d9b78..3b43483 100644
--- a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
+++ b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
@@ -74,6 +74,16 @@
  * Dispatches execution onto Swing event dispatching thread and provides native [delay] support.
  */
 internal object Swing : SwingDispatcher() {
+
+    /* A workaround so that the dispatcher's initialization crashes with an exception if running in a headless
+    environment. This is needed so that this broken dispatcher is not used as the source of delays. */
+    init {
+        Timer(1) { }.apply {
+            isRepeats = false
+            start()
+        }
+    }
+
     override val immediate: MainCoroutineDispatcher
         get() = ImmediateSwingDispatcher