Merge branch 'develop'
diff --git a/CHANGES.md b/CHANGES.md
index b5be24e..5054cac 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,52 @@
# Change log for kotlinx.coroutines
+## Version 0.19
+
+* This release is published to Maven Central.
+* `DefaultDispatcher` is introduced (see #136):
+ * `launch`, `async`, `produce`, `actor` and other integration-specific coroutine builders now use
+ `DefaultDispatcher` as the default value for their `context` parameter.
+ * When a context is explicitly specified, `newCoroutineContext` function checks if there is any
+ interceptor/dispatcher defined in the context and uses `DefaultDispatcher` if there is none.
+ * `DefaultDispatcher` is currently defined to be equal to `CommonPool`.
+ * Examples in the [guide](coroutines-guide.md) now start with `launch { ... }` code and explanation on the nature
+ and the need for coroutine context starts in "Coroutine context and dispatchers" section.
+* Parent coroutines now wait for their children (see #125):
+ * Job _completing_ state is introduced in documentation as a state in which parent coroutine waits for its children.
+ * `Job.attachChild` and `Job.cancelChildren` are introduced.
+ * `Job.join` now always checks cancellation status of invoker coroutine for predictable behavior when joining
+ failed child coroutine.
+ * `Job.cancelAndJoin` extension is introduced.
+ * `CoroutineContext.cancel` and `CoroutineContext.cancelChildren` extensions are introduced for convenience.
+ * `withTimeout`/`withTimeoutOrNull` blocks become proper coroutines that have `CoroutineScope` and wait for children, too.
+ * Diagnostics in cancellation and unexpected exception messages are improved,
+ coroutine name is included in debug mode.
+ * Fixed cancellable suspending functions to throw `CancellationException` (as was documented before) even when
+ the coroutine is cancelled with another application-specific exception.
+ * `JobCancellationException` is introduced as a specific subclass of `CancellationException` which is
+ used for coroutines that are cancelled without cause and to wrap application-specific exceptions.
+ * `Job.getCompletionException` is renamed to `Job.getCancellationException` and return a wrapper exception if needed.
+ * Introduced `Deferred.getCompletionExceptionOrNull` to get not-wrapped exception result of `async` task.
+ * Updated docs for `Job` & `Deferred` to explain parent/child relations.
+* `select` expression is modularized:
+ * `SelectClause(0,1,2)` interfaces are introduced, so that synchronization
+ constructs can define their select clauses without having to modify
+ the source of the `SelectBuilder` in `kotlinx-corounes-core` module.
+ * `Job.onJoin`, `Deferred.onAwait`, `Mutex.onLock`, `SendChannel.onSend`, `ReceiveChannel.onReceive`, etc
+ that were functions before are now properties returning the corresponding select clauses. Old functions
+ are left in bytecode for backwards compatibility on use-site, but any outside code that was implementing those
+ interfaces by itself must be updated.
+ * This opens road to moving channels into a separate module in future updates.
+* Renamed `TimeoutException` to `TimeoutCancellationException` (old name is deprecated).
+* Fixed various minor problems:
+ * JavaFx toolkit is now initialized by `JavaFx` context (see #108).
+ * Fixed lost ACC_STATIC on <clinit> methods (see #116).
+ * Fixed link to source code from documentation (see #129).
+ * Fixed `delay` in arbitrary contexts (see #133).
+* `kotlinx-coroutines-io` module is introduced. It is a work-in-progress on `ByteReadChannel` and `ByteWriteChannel`
+ interfaces, their implementations, and related classes to enable convenient coroutine integration with various
+ asynchronous I/O libraries and sockets. It is currently _unstable_ and **will change** in the next release.
+
## Version 0.18
* Kotlin 1.1.4 is required to use this version, which enables:
diff --git a/README.md b/README.md
index eee1687..1749825 100644
--- a/README.md
+++ b/README.md
@@ -2,14 +2,14 @@
[](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[](http://www.apache.org/licenses/LICENSE-2.0)
-[ ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/0.18)
+[ ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/0.19)
Library support for Kotlin coroutines.
This is a companion version for Kotlin 1.1.4 release (this is the minimal required Kotlin runtime version).
## Modules
-* [kotlinx-coroutines-core](kotlinx-coroutines-core/README.md) -- core primitives to work with coroutines:
+* [core](core/README.md) -- core primitives to work with coroutines:
* `launch`, `async`, `produce`, `actor`, etc coroutine builders;
* `Job` and `Deferred` light-weight future with cancellation support;
* `CommonPool` and other coroutine contexts;
@@ -39,62 +39,43 @@
> Note that these libraries are experimental and are subject to change.
-The libraries are published to [kotlinx](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines) bintray repository
-and also linked to [JCenter](https://bintray.com/bintray/jcenter?filterByPkgName=kotlinx.coroutines).
-
-These libraries require kotlin compiler version `1.1.4` or later and
-require kotlin runtime of the same version as a dependency.
+The libraries are published to [kotlinx](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines) bintray repository,
+linked to [JCenter](https://bintray.com/bintray/jcenter?filterByPkgName=kotlinx.coroutines) and
+pushed to [Maven Central](https://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.jetbrains.kotlinx%20a%3Akotlinx-coroutines).
### Maven
-Add Bintray JCenter repository to `<repositories>` section:
-
-```xml
-<repository>
- <id>central</id>
- <url>http://jcenter.bintray.com</url>
-</repository>
-```
-
Add dependencies (you can also add other modules that you need):
```xml
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
- <version>0.18</version>
+ <version>0.19</version>
</dependency>
```
-And make sure that you use the right Kotlin version:
+And make sure that you use the latest Kotlin version:
```xml
<properties>
- <kotlin.version>1.1.4</kotlin.version>
+ <kotlin.version>1.1.51</kotlin.version>
</properties>
```
### Gradle
-Add Bintray JCenter repository:
-
-```groovy
-repositories {
- jcenter()
-}
-```
-
Add dependencies (you can also add other modules that you need):
```groovy
-compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.18'
+compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19'
```
-And make sure that you use the right Kotlin version:
+And make sure that you use the latest Kotlin version:
```groovy
buildscript {
- ext.kotlin_version = '1.1.4'
+ ext.kotlin_version = '1.1.51'
}
```
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
index fde11e7..3e34b15 100644
--- a/benchmarks/pom.xml
+++ b/benchmarks/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
</parent>
<artifactId>benchmarks</artifactId>
diff --git a/core/README.md b/core/README.md
new file mode 100644
index 0000000..95ac13b
--- /dev/null
+++ b/core/README.md
@@ -0,0 +1,9 @@
+# Coroutines core
+
+This directory contains modules that provide core coroutine support.
+
+## Modules
+
+* [kotlinx-coroutines-core](kotlinx-coroutines-core/README.md) -- core coroutine builders and synchronization primitives.
+* [kotlinx-coroutines-io](kotlinx-coroutines-io/README.md) -- byte I/O channels (_unstable_, work in progress).
+
diff --git a/kotlinx-coroutines-core/README.md b/core/kotlinx-coroutines-core/README.md
similarity index 86%
rename from kotlinx-coroutines-core/README.md
rename to core/kotlinx-coroutines-core/README.md
index 89626bb..131becb 100644
--- a/kotlinx-coroutines-core/README.md
+++ b/core/kotlinx-coroutines-core/README.md
@@ -16,6 +16,7 @@
| **Name** | **Description**
| --------------------------- | ---------------
+| [DefaultDispatcher] | Is equal to [CommonPool]
| [CommonPool] | Confines coroutine execution to a shared pool of threads
| [newSingleThreadContext] | Create new single-threaded coroutine context
| [newFixedThreadPoolContext] | Creates new thread pool of a fixed size
@@ -50,12 +51,12 @@
| **Receiver** | **Suspending function** | **Select clause** | **Non-suspending version**
| ---------------- | --------------------------------------------- | ------------------------------------------------ | --------------------------
-| [Job] | [join][Job.join] | [onJoin][kotlinx.coroutines.experimental.selects.SelectBuilder.onJoin] | [isCompleted][Job.isCompleted]
-| [Deferred] | [await][Deferred.await] | [onAwait][kotlinx.coroutines.experimental.selects.SelectBuilder.onAwait] | [isCompleted][Job.isCompleted]
-| [SendChannel][kotlinx.coroutines.experimental.channels.SendChannel] | [send][kotlinx.coroutines.experimental.channels.SendChannel.send] | [onSend][kotlinx.coroutines.experimental.selects.SelectBuilder.onSend] | [offer][kotlinx.coroutines.experimental.channels.SendChannel.offer]
-| [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [receive][kotlinx.coroutines.experimental.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.experimental.selects.SelectBuilder.onReceive] | [poll][kotlinx.coroutines.experimental.channels.ReceiveChannel.poll]
-| [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.experimental.channels.ReceiveChannel.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.experimental.selects.SelectBuilder.onReceiveOrNull] | [poll][kotlinx.coroutines.experimental.channels.ReceiveChannel.poll]
-| [Mutex][kotlinx.coroutines.experimental.sync.Mutex] | [lock][kotlinx.coroutines.experimental.sync.Mutex.lock] | [onLock][kotlinx.coroutines.experimental.selects.SelectBuilder.onLock] | [tryLock][kotlinx.coroutines.experimental.sync.Mutex.tryLock]
+| [Job] | [join][Job.join] | [onJoin][Job.onJoin] | [isCompleted][Job.isCompleted]
+| [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted]
+| [SendChannel][kotlinx.coroutines.experimental.channels.SendChannel] | [send][kotlinx.coroutines.experimental.channels.SendChannel.send] | [onSend][kotlinx.coroutines.experimental.channels.SendChannel.onSend] | [offer][kotlinx.coroutines.experimental.channels.SendChannel.offer]
+| [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [receive][kotlinx.coroutines.experimental.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.experimental.channels.ReceiveChannel.onReceive] | [poll][kotlinx.coroutines.experimental.channels.ReceiveChannel.poll]
+| [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.experimental.channels.ReceiveChannel.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.experimental.channels.ReceiveChannel.onReceiveOrNull] | [poll][kotlinx.coroutines.experimental.channels.ReceiveChannel.poll]
+| [Mutex][kotlinx.coroutines.experimental.sync.Mutex] | [lock][kotlinx.coroutines.experimental.sync.Mutex.lock] | [onLock][kotlinx.coroutines.experimental.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.experimental.sync.Mutex.tryLock]
| none | [delay] | [onTimeout][kotlinx.coroutines.experimental.selects.SelectBuilder.onTimeout] | none
Cancellation support for user-defined suspending functions is available with [suspendCancellableCoroutine]
@@ -91,6 +92,7 @@
[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-dispatcher/index.html
+[DefaultDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-default-dispatcher.html
[CommonPool]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-common-pool/index.html
[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-single-thread-context.html
[newFixedThreadPoolContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-fixed-thread-pool-context.html
@@ -104,13 +106,16 @@
[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout.html
[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout-or-null.html
[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/join.html
+[Job.onJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/on-join.html
[Job.isCompleted]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/is-completed.html
[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/await.html
+[Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/on-await.html
[suspendCancellableCoroutine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/suspend-cancellable-coroutine.html
[newCoroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-coroutine-context.html
<!--- INDEX kotlinx.coroutines.experimental.sync -->
[kotlinx.coroutines.experimental.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/index.html
[kotlinx.coroutines.experimental.sync.Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/lock.html
+[kotlinx.coroutines.experimental.sync.Mutex.onLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/on-lock.html
[kotlinx.coroutines.experimental.sync.Mutex.tryLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/try-lock.html
<!--- INDEX kotlinx.coroutines.experimental.channels -->
[kotlinx.coroutines.experimental.channels.produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html
@@ -123,17 +128,14 @@
[kotlinx.coroutines.experimental.channels.SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/send.html
[kotlinx.coroutines.experimental.channels.ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/receive.html
[kotlinx.coroutines.experimental.channels.SendChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/index.html
+[kotlinx.coroutines.experimental.channels.SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/on-send.html
[kotlinx.coroutines.experimental.channels.SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/offer.html
[kotlinx.coroutines.experimental.channels.ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/index.html
+[kotlinx.coroutines.experimental.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/on-receive.html
[kotlinx.coroutines.experimental.channels.ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/poll.html
[kotlinx.coroutines.experimental.channels.ReceiveChannel.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/receive-or-null.html
+[kotlinx.coroutines.experimental.channels.ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/on-receive-or-null.html
<!--- INDEX kotlinx.coroutines.experimental.selects -->
[kotlinx.coroutines.experimental.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/select.html
-[kotlinx.coroutines.experimental.selects.SelectBuilder.onJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-join.html
-[kotlinx.coroutines.experimental.selects.SelectBuilder.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-await.html
-[kotlinx.coroutines.experimental.selects.SelectBuilder.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-send.html
-[kotlinx.coroutines.experimental.selects.SelectBuilder.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-receive.html
-[kotlinx.coroutines.experimental.selects.SelectBuilder.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-receive-or-null.html
-[kotlinx.coroutines.experimental.selects.SelectBuilder.onLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-lock.html
[kotlinx.coroutines.experimental.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-timeout.html
<!--- END -->
diff --git a/kotlinx-coroutines-core/pom.xml b/core/kotlinx-coroutines-core/pom.xml
similarity index 95%
rename from kotlinx-coroutines-core/pom.xml
rename to core/kotlinx-coroutines-core/pom.xml
index 4858b7a..48e5477 100644
--- a/kotlinx-coroutines-core/pom.xml
+++ b/core/kotlinx-coroutines-core/pom.xml
@@ -22,7 +22,8 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-core</artifactId>
@@ -31,6 +32,7 @@
<properties>
<!-- makes sure core is portable to lowest common denominator -->
<kotlin.compiler.jdkHome>${env.JDK_16}</kotlin.compiler.jdkHome>
+ <subdir>core</subdir>
</properties>
<repositories>
@@ -86,7 +88,7 @@
<forkMode>once</forkMode>
<classesDirectory>${project.build.directory}/classes-atomicfu</classesDirectory>
<includes>
- <include>**/*LFTest.java</include>
+ <include>**/*LFTest.*</include>
</includes>
</configuration>
</execution>
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt
diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt
new file mode 100644
index 0000000..eca5f1a
--- /dev/null
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental
+
+import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.CoroutineContext
+
+/**
+ * Abstract class for coroutines.
+ *
+ * * Coroutines implement completion [Continuation], [Job], and [CoroutineScope] interfaces.
+ * * Coroutine stores the result of continuation in the state of the job.
+ * * Coroutine waits for children coroutines to finish before completing.
+ * * Coroutines are cancelled through an intermediate _cancelling_ state.
+ *
+ * @param active when `true` coroutine is created in _active_ state, when `false` in _new_ state. See [Job] for details.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+public abstract class AbstractCoroutine<in T>(
+ private val parentContext: CoroutineContext,
+ active: Boolean
+) : JobSupport(active), Continuation<T>, CoroutineScope {
+ @Suppress("LeakingThis")
+ public final override val context: CoroutineContext = parentContext + this
+ public final override val coroutineContext: CoroutineContext get() = context
+
+ // all coroutines are cancelled through an intermediate cancelling state
+ final override val hasCancellingState: Boolean get() = true
+
+ protected open val defaultResumeMode: Int get() = MODE_ATOMIC_DEFAULT
+
+ final override fun resume(value: T) {
+ makeCompleting(value, defaultResumeMode)
+ }
+
+ final override fun resumeWithException(exception: Throwable) {
+ makeCompleting(CompletedExceptionally(exception), defaultResumeMode)
+ }
+
+ final override fun handleException(exception: Throwable) {
+ handleCoroutineException(parentContext, exception)
+ }
+
+ override fun nameString(): String {
+ val coroutineName = context.coroutineName ?: return super.nameString()
+ return "\"$coroutineName\":${super.nameString()}"
+ }
+}
+
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt
similarity index 88%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt
index 6c19bcb..9ed1665 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt
@@ -28,10 +28,11 @@
* Launches new coroutine without blocking current thread and returns a reference to the coroutine as a [Job].
* The coroutine is cancelled when the resulting job is [cancelled][Job.cancel].
*
- * The [context] for the new coroutine must be explicitly specified.
- * See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
*
* By default, the coroutine is immediately scheduled for execution.
* Other options can be specified via `start` parameter. See [CoroutineStart] for details.
@@ -45,12 +46,12 @@
*
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
- * @param context context of the coroutine
- * @param start coroutine start option
- * @param block the coroutine code
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the coroutine code.
*/
public fun launch(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
@@ -83,7 +84,7 @@
* a current one, otherwise it is ignored.
* By default, the coroutine is immediately scheduled for execution and can be cancelled
* while it is waiting to be executed and it can be cancelled while the result is scheduled
- * to be be processed by the invoker context.
+ * to be processed by the invoker context.
* Other options can be specified via `start` parameter. See [CoroutineStart] for details.
* A value of [CoroutineStart.LAZY] is not supported and produces [IllegalArgumentException].
*/
@@ -137,15 +138,17 @@
* this `runBlocking` invocation throws [InterruptedException].
*
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
+ *
+ * @param context context of the coroutine. The default value is an implementation of [EventLoop].
+ * @param block the coroutine code.
*/
@Throws(InterruptedException::class)
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
val currentThread = Thread.currentThread()
- val eventLoop = if (context[ContinuationInterceptor] == null) EventLoopImpl(currentThread) else null
+ val eventLoop = if (context[ContinuationInterceptor] == null) BlockingEventLoop(currentThread) else null
val newContext = newCoroutineContext(context + (eventLoop ?: EmptyCoroutineContext))
val coroutine = BlockingCoroutine<T>(newContext, currentThread, privateEventLoop = eventLoop != null)
coroutine.initParentJob(context[Job])
- eventLoop?.initParentJob(coroutine)
block.startCoroutine(coroutine, coroutine)
return coroutine.joinBlocking()
}
@@ -156,9 +159,9 @@
private val parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
- override fun afterCompletion(state: Any?, mode: Int) {
+ override fun onCancellation(exceptionally: CompletedExceptionally?) {
// note the use of the parent's job context below!
- if (state is CompletedExceptionally) handleCoroutineException(parentContext, state.exception)
+ if (exceptionally != null) handleCoroutineException(parentContext, exceptionally.exception)
}
}
@@ -209,10 +212,14 @@
private val eventLoop: EventLoop? = parentContext[ContinuationInterceptor] as? EventLoop
init {
- if (privateEventLoop) require(eventLoop is EventLoopImpl)
+ if (privateEventLoop) require(eventLoop is BlockingEventLoop)
}
override fun afterCompletion(state: Any?, mode: Int) {
+ // signal termination to event loop (don't accept more tasks)
+ if (privateEventLoop)
+ (eventLoop as BlockingEventLoop).isCompleted = true
+ // wake up blocked thread
if (Thread.currentThread() != blockedThread)
LockSupport.unpark(blockedThread)
}
@@ -228,11 +235,12 @@
timeSource.parkNanos(this, parkNanos)
}
// process queued events (that could have been added after last processNextEvent and before cancel
- if (privateEventLoop) (eventLoop as EventLoopImpl).shutdown()
+ if (privateEventLoop) (eventLoop as BlockingEventLoop).shutdown()
timeSource.unregisterTimeLoopThread()
// now return result
val state = this.state
(state as? CompletedExceptionally)?.let { throw it.exception }
return state as T
}
+
}
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Cancellable.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Cancellable.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Cancellable.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Cancellable.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
similarity index 94%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
index b90973a..2311193 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
@@ -162,7 +162,7 @@
* @suppress **This is unstable API and it is subject to change.**
*/
public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNode): DisposableHandle =
- invokeOnCompletion(RemoveOnCancel(this, node))
+ invokeOnCompletion(handler = RemoveOnCancel(this, node))
// --------------- implementation details ---------------
@@ -245,20 +245,21 @@
}
override fun completeResume(token: Any) {
- completeUpdateState(token, state, resumeMode)
+ completeUpdateState(token as Incomplete, state, resumeMode)
}
override fun CoroutineDispatcher.resumeUndispatched(value: T) {
- val dc = delegate as? DispatchedContinuation ?: throw IllegalArgumentException("Must be used with DispatchedContinuation")
- resumeImpl(value, if (dc.dispatcher === this) MODE_UNDISPATCHED else resumeMode)
+ val dc = delegate as? DispatchedContinuation
+ resumeImpl(value, if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode)
}
override fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable) {
- val dc = delegate as? DispatchedContinuation ?: throw IllegalArgumentException("Must be used with DispatchedContinuation")
- resumeWithExceptionImpl(exception, if (dc.dispatcher === this) MODE_UNDISPATCHED else resumeMode)
+ val dc = delegate as? DispatchedContinuation
+ resumeWithExceptionImpl(exception, if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode)
}
- override fun toString(): String = super.toString() + "[${delegate.toDebugString()}]"
+ override fun nameString(): String =
+ "CancellableContinuation(${delegate.toDebugString()})"
}
private class CompletedIdempotentResult(
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt
similarity index 95%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt
index f58b7ac..106fe97 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt
@@ -16,7 +16,7 @@
package kotlinx.coroutines.experimental
-import kotlinx.coroutines.experimental.selects.SelectInstance
+import kotlinx.coroutines.experimental.selects.SelectClause1
/**
* A [Deferred] that can be completed via public functions
@@ -86,8 +86,8 @@
private class CompletableDeferredImpl<T> : JobSupport(true), CompletableDeferred<T> {
override fun getCompleted(): T = getCompletedInternal() as T
suspend override fun await(): T = awaitInternal() as T
- override fun <R> registerSelectAwait(select: SelectInstance<R>, block: suspend (T) -> R) =
- registerSelectAwaitInternal(select, block as (suspend (Any?) -> R))
+ override val onAwait: SelectClause1<T>
+ get() = this as SelectClause1<T>
override fun complete(value: T): Boolean {
loopOnState { state ->
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt
similarity index 74%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt
index 602b9b9..606ac4e 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt
@@ -18,6 +18,7 @@
import java.util.concurrent.atomic.AtomicLong
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
private const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug"
@@ -65,7 +66,16 @@
public typealias Here = Unconfined
/**
- * Creates context for the new coroutine with optional support for debugging facilities (when turned on).
+ * This is the default [CoroutineDispatcher] that is used by all standard builders like
+ * [launch], [async], etc if no dispatcher nor any other [ContinuationInterceptor] is specified in their context.
+ *
+ * It is currently equal to [CommonPool], but the value is subject to change in the future.
+ */
+public val DefaultDispatcher: CoroutineDispatcher = CommonPool
+
+/**
+ * Creates context for the new coroutine. It installs [DefaultDispatcher] when no other dispatcher nor
+ * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on).
*
* **Debugging facilities:** In debug mode every coroutine is assigned a unique consecutive identifier.
* Every thread that executes a coroutine has its name modified to include the name and identifier of the
@@ -82,40 +92,50 @@
* Coroutine name can be explicitly assigned using [CoroutineName] context element.
* The string "coroutine" is used as a default name.
*/
-public fun newCoroutineContext(context: CoroutineContext): CoroutineContext =
- if (DEBUG) context + CoroutineId(COROUTINE_ID.incrementAndGet()) else context
+public fun newCoroutineContext(context: CoroutineContext): CoroutineContext {
+ val debug = if (DEBUG) context + CoroutineId(COROUTINE_ID.incrementAndGet()) else context
+ return if (context !== DefaultDispatcher && context[ContinuationInterceptor] == null)
+ debug + DefaultDispatcher else debug
+}
/**
* Executes a block using a given coroutine context.
*/
internal inline fun <T> withCoroutineContext(context: CoroutineContext, block: () -> T): T {
- val oldName = updateContext(context)
+ val oldName = context.updateThreadContext()
try {
return block()
} finally {
- restoreContext(oldName)
+ restoreThreadContext(oldName)
}
}
@PublishedApi
-internal fun updateContext(context: CoroutineContext): String? {
+internal fun CoroutineContext.updateThreadContext(): String? {
if (!DEBUG) return null
- val newId = context[CoroutineId] ?: return null
+ val coroutineId = this[CoroutineId] ?: return null
+ val coroutineName = this[CoroutineName]?.name ?: "coroutine"
val currentThread = Thread.currentThread()
val oldName = currentThread.name
- val coroutineName = context[CoroutineName]?.name ?: "coroutine"
currentThread.name = buildString(oldName.length + coroutineName.length + 10) {
append(oldName)
append(" @")
append(coroutineName)
append('#')
- append(newId.id)
+ append(coroutineId.id)
}
return oldName
}
+internal val CoroutineContext.coroutineName: String? get() {
+ if (!DEBUG) return null
+ val coroutineId = this[CoroutineId] ?: return null
+ val coroutineName = this[CoroutineName]?.name ?: "coroutine"
+ return "$coroutineName#${coroutineId.id}"
+}
+
@PublishedApi
-internal fun restoreContext(oldName: String?) {
+internal fun restoreThreadContext(oldName: String?) {
if (oldName != null) Thread.currentThread().name = oldName
}
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt
similarity index 97%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt
index 6176392..2abe2aa 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt
@@ -30,6 +30,8 @@
* The coroutine will resume in whatever thread that is used by the
* corresponding suspending function, without confining it to any specific thread or pool.
* This in an appropriate choice for IO-intensive coroutines that do not consume CPU resources.
+ * * [DefaultDispatcher] -- is used by all standard builder if no dispatcher nor any other [ContinuationInterceptor]
+ * is specified in their context. It is currently equal to [CommonPool] (subject to change).
* * [CommonPool] -- immediately returns from the coroutine builder and schedules coroutine execution to
* a common pool of shared background threads.
* This is an appropriate choice for compute-intensive coroutines that consume a lot of CPU resources.
@@ -115,7 +117,7 @@
val job = if (cancellable) context[Job] else null
withCoroutineContext(context) {
when {
- job != null && !job.isActive -> continuation.resumeWithException(job.getCompletionException())
+ job != null && !job.isActive -> continuation.resumeWithException(job.getCancellationException())
exception -> continuation.resumeWithException(value as Throwable)
else -> continuation.resume(value as T)
}
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt
similarity index 82%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt
index 8e83d62..609f092 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt
@@ -22,13 +22,14 @@
/**
* Helper function for coroutine builder implementations to handle uncaught exception in coroutines.
+ *
* It tries to handle uncaught exception in the following way:
* * If there is [CoroutineExceptionHandler] in the context, then it is used.
* * Otherwise, if exception is [CancellationException] then it is ignored
* (because that is the supposed mechanism to cancel the running coroutine)
- * * Otherwise, if there is a [Job] in the context, then [Job.cancel] is invoked and if it
- * returns `true` (it was still active), then the exception is considered to be handled.
- * * Otherwise, current thread's [Thread.uncaughtExceptionHandler] is used.
+ * * Otherwise:
+ * * if there is a [Job] in the context, then [Job.cancel] is invoked;
+ * * and current thread's [Thread.uncaughtExceptionHandler] is invoked.
*/
fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
context[CoroutineExceptionHandler]?.let {
@@ -37,15 +38,23 @@
}
// ignore CancellationException (they are normal means to terminate a coroutine)
if (exception is CancellationException) return
- // quit if successfully pushed exception as cancellation reason
- if (context[Job]?.cancel(exception) ?: false) return
- // otherwise just use thread's handler
+ // try cancel job in the context
+ context[Job]?.cancel(exception)
+ // use thread's handler
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
/**
* An optional element on the coroutine context to handle uncaught exceptions.
+ *
+ * By default, when no handler is installed, uncaught exception are handled in the following way:
+ * * If exception is [CancellationException] then it is ignored
+ * (because that is the supposed mechanism to cancel the running coroutine)
+ * * Otherwise:
+ * * if there is a [Job] in the context, then [Job.cancel] is invoked;
+ * * and current thread's [Thread.uncaughtExceptionHandler] is invoked.
+ *
* See [handleCoroutineException].
*/
public interface CoroutineExceptionHandler : CoroutineContext.Element {
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineName.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineName.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineName.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineName.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineStart.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineStart.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineStart.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineStart.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/DefaultExecutor.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/DefaultExecutor.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/DefaultExecutor.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/DefaultExecutor.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt
similarity index 72%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt
index b1dfa39..0f2524d 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt
@@ -16,9 +16,9 @@
package kotlinx.coroutines.experimental
-import kotlinx.coroutines.experimental.selects.SelectBuilder
-import kotlinx.coroutines.experimental.selects.SelectInstance
+import kotlinx.coroutines.experimental.selects.SelectClause1
import kotlinx.coroutines.experimental.selects.select
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
/**
@@ -33,6 +33,7 @@
* | --------------------------------------- | ---------- | ------------- | -------------------------- | ------------- |
* | _New_ (optional initial state) | `false` | `false` | `false` | `false` |
* | _Active_ (default initial state) | `true` | `false` | `false` | `false` |
+ * | _Completing_ (optional transient state) | `true` | `false` | `false` | `false` |
* | _Cancelling_ (optional transient state) | `false` | `false` | `false` | `true` |
* | _Cancelled_ (final state) | `false` | `true` | `true` | `true` |
* | _Resolved_ (final state) | `false` | `true` | `false` | `false` |
@@ -47,18 +48,19 @@
* _cancelling_ state immediately. A simple implementation of deferred -- [CompletableDeferred],
* that is not backed by a coroutine, does not have a _cancelling_ state, but becomes _cancelled_
* on [cancel] immediately. Coroutines, on the other hand, become _cancelled_ only when they finish
- * executing their code.
+ * executing their code and after all their [children][attachChild] complete.
*
* ```
- * +-----+ start +--------+ complete +-----------+
- * | New | ---------------> | Active | ---------+-> | Resolved |
- * +-----+ +--------+ | |(completed)|
- * | | | +-----------+
- * | cancel | cancel |
- * V V | +-----------+
- * +-----------+ finish +------------+ +-> | Failed |
- * | Cancelled | <--------- | Cancelling | |(completed)|
- * |(completed)| +------------+ +-----------+
+ * wait children
+ * +-----+ start +--------+ complete +-------------+ finish +-----------+
+ * | New | ---------------> | Active | ----------> | Completing | ---+-> | Resolved |
+ * +-----+ +--------+ +-------------+ | |(completed)|
+ * | | | | +-----------+
+ * | cancel | cancel | cancel |
+ * V V | | +-----------+
+ * +-----------+ finish +------------+ | +-> | Failed |
+ * | Cancelled | <--------- | Cancelling | <---------------+ |(completed)|
+ * |(completed)| +------------+ +-----------+
* +-----------+
* ```
*
@@ -69,7 +71,9 @@
* or the cancellation cause inside the coroutine.
*
* A deferred value can have a _parent_ job. A deferred value with a parent is cancelled when its parent is
- * cancelled or completes.
+ * cancelled or completes. Parent waits for all its [children][attachChild] to complete in _completing_ or
+ * _cancelling_ state. _Completing_ state is purely internal. For an outside observer a _completing_
+ * deferred is still active, while internally it is waiting for its children.
*
* All functions on this interface and on all interfaces derived from it are **thread-safe** and can
* be safely invoked from concurrent coroutines without external synchronization.
@@ -91,16 +95,17 @@
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* immediately resumes with [CancellationException].
*
- * This function can be used in [select] invocation with [onAwait][SelectBuilder.onAwait] clause.
+ * This function can be used in [select] invocation with [onAwait] clause.
* Use [isCompleted] to check for completion of this deferred value without waiting.
*/
public suspend fun await(): T
/**
- * Registers [onAwait][SelectBuilder.onAwait] select clause.
- * @suppress **This is unstable API and it is subject to change.**
+ * Clause for [select] expression of [await] suspending function that selects with the deferred value when it is
+ * resolved. The [select] invocation fails if the deferred value completes exceptionally (either fails or
+ * it cancelled).
*/
- public fun <R> registerSelectAwait(select: SelectInstance<R>, block: suspend (T) -> R)
+ public val onAwait: SelectClause1<T>
/**
* Returns *completed* result or throws [IllegalStateException] if this deferred value has not
@@ -108,11 +113,21 @@
* [completed exceptionally][isCompletedExceptionally].
*
* This function is designed to be used from [invokeOnCompletion] handlers, when there is an absolute certainty that
- * the value is already complete.
+ * the value is already complete. See also [getCompletionExceptionOrNull].
*/
public fun getCompleted(): T
/**
+ * Returns *completion exception* result if this deferred [completed exceptionally][isCompletedExceptionally],
+ * `null` if it is completed normally, or throws [IllegalStateException] if this deferred value has not
+ * [completed][isCompleted] yet.
+ *
+ * This function is designed to be used from [invokeOnCompletion] handlers, when there is an absolute certainty that
+ * the value is already complete. See also [getCompleted].
+ */
+ public fun getCompletionExceptionOrNull(): Throwable?
+
+ /**
* @suppress **Deprecated**: Use `isActive`.
*/
@Deprecated(message = "Use `isActive`", replaceWith = ReplaceWith("isActive"))
@@ -123,10 +138,12 @@
* Creates new coroutine and returns its future result as an implementation of [Deferred].
*
* The running coroutine is cancelled when the resulting object is [cancelled][Job.cancel].
- * The [context] for the new coroutine must be explicitly specified.
- * See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
*
* By default, the coroutine is immediately scheduled for execution.
* Other options can be specified via `start` parameter. See [CoroutineStart] for details.
@@ -134,12 +151,12 @@
* the resulting [Deferred] is created in _new_ state. It can be explicitly started with [start][Job.start]
* function and will be started implicitly on the first invocation of [join][Job.join] or [await][Deferred.await].
*
- * @param context context of the coroutine
- * @param start coroutine start option
- * @param block the coroutine code
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the coroutine code.
*/
public fun <T> async(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
@@ -175,8 +192,8 @@
) : AbstractCoroutine<T>(parentContext, active), Deferred<T> {
override fun getCompleted(): T = getCompletedInternal() as T
suspend override fun await(): T = awaitInternal() as T
- override fun <R> registerSelectAwait(select: SelectInstance<R>, block: suspend (T) -> R) =
- registerSelectAwaitInternal(select, block as (suspend (Any?) -> R))
+ override val onAwait: SelectClause1<T>
+ get() = this as SelectClause1<T>
}
private class LazyDeferredCoroutine<T>(
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt
similarity index 96%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt
index 0c2c9c5..26e8ffa 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt
@@ -247,20 +247,11 @@
}
}
-internal class EventLoopImpl(
+internal abstract class ThreadEventLoop(
private val thread: Thread
) : EventLoopBase() {
- private var parentJob: Job? = null
-
- override val canComplete: Boolean get() = parentJob != null
- override val isCompleted: Boolean get() = parentJob?.isCompleted == true
override fun isCorrectThread(): Boolean = Thread.currentThread() === thread
- fun initParentJob(coroutine: Job) {
- require(this.parentJob == null)
- this.parentJob = coroutine
- }
-
override fun unpark() {
if (Thread.currentThread() !== thread)
timeSource.unpark(thread)
@@ -274,5 +265,23 @@
// reschedule the rest of delayed tasks
rescheduleAllDelayed()
}
+
}
+private class EventLoopImpl(thread: Thread) : ThreadEventLoop(thread) {
+ private var parentJob: Job? = null
+
+ override val canComplete: Boolean get() = parentJob != null
+ override val isCompleted: Boolean get() = parentJob?.isCompleted == true
+
+ fun initParentJob(parentJob: Job) {
+ require(this.parentJob == null)
+ this.parentJob = parentJob
+ }
+}
+
+internal class BlockingEventLoop(thread: Thread) : ThreadEventLoop(thread) {
+ override val canComplete: Boolean get() = true
+ @Volatile
+ public override var isCompleted: Boolean = false
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt
diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt
new file mode 100644
index 0000000..fef9fa6
--- /dev/null
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt
@@ -0,0 +1,1414 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental
+
+import kotlinx.atomicfu.atomic
+import kotlinx.atomicfu.loop
+import kotlinx.coroutines.experimental.internal.LockFreeLinkedListHead
+import kotlinx.coroutines.experimental.internal.LockFreeLinkedListNode
+import kotlinx.coroutines.experimental.internal.OpDescriptor
+import kotlinx.coroutines.experimental.internal.unwrap
+import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
+import kotlinx.coroutines.experimental.selects.SelectClause0
+import kotlinx.coroutines.experimental.selects.SelectClause1
+import kotlinx.coroutines.experimental.selects.SelectInstance
+import kotlinx.coroutines.experimental.selects.select
+import java.util.concurrent.Future
+import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.CoroutineContext
+import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
+
+// --------------- core job interfaces ---------------
+
+/**
+ * A background job. Conceptually, a job is a cancellable thing with a simple life-cycle that
+ * culminates in its completion. Jobs can be arranged into parent-child hierarchies where cancellation
+ * or completion of parent immediately cancels all its children.
+ *
+ * The most basic instances of [Job] are created with [launch] coroutine builder or with a
+ * `Job()` factory function. Other coroutine builders and primitives like
+ * [Deferred] also implement [Job] interface.
+ *
+ * A job has the following states:
+ *
+ * | **State** | [isActive] | [isCompleted] | [isCancelled] |
+ * | --------------------------------------- | ---------- | ------------- | ------------- |
+ * | _New_ (optional initial state) | `false` | `false` | `false` |
+ * | _Active_ (default initial state) | `true` | `false` | `false` |
+ * | _Completing_ (optional transient state) | `true` | `false` | `false` |
+ * | _Cancelling_ (optional transient state) | `false` | `false` | `true` |
+ * | _Cancelled_ (final state) | `false` | `true` | `true` |
+ * | _Completed normally_ (final state) | `false` | `true` | `false` |
+ *
+ * Usually, a job is created in _active_ state (it is created and started). However, coroutine builders
+ * that provide an optional `start` parameter create a coroutine in _new_ state when this parameter is set to
+ * [CoroutineStart.LAZY]. Such a job can be made _active_ by invoking [start] or [join].
+ *
+ * A job can be _cancelled_ at any time with [cancel] function that forces it to transition to
+ * _cancelling_ state immediately. Job that is not backed by a coroutine and does not have
+ * [children][attachChild] becomes _cancelled_ on [cancel] immediately.
+ * Otherwise, job becomes _cancelled_ when it finishes executing its code and
+ * when all its children [complete][isCompleted].
+ *
+ * ```
+ * wait children
+ * +-----+ start +--------+ complete +-------------+ finish +-----------+
+ * | New | ---------------> | Active | -----------> | Completing | -------> | Completed |
+ * +-----+ +--------+ +-------------+ | normally |
+ * | | | +-----------+
+ * | cancel | cancel | cancel
+ * V V |
+ * +-----------+ finish +------------+ |
+ * | Cancelled | <--------- | Cancelling | <----------------+
+ * |(completed)| +------------+
+ * +-----------+
+ * ```
+ *
+ * A job in the [coroutineContext][CoroutineScope.coroutineContext] represents the coroutine itself.
+ * A job is active while the coroutine is working and job's cancellation aborts the coroutine when
+ * the coroutine is suspended on a _cancellable_ suspension point by throwing [CancellationException].
+ *
+ * A job can have a _parent_ job. A job with a parent is cancelled when its parent is cancelled or completes.
+ * Parent job waits for all its [children][attachChild] to complete in _completing_ or _cancelling_ state.
+ * _Completing_ state is purely internal to the job. For an outside observer a _completing_ job is still active,
+ * while internally it is waiting for its children.
+ *
+ * All functions on this interface and on all interfaces derived from it are **thread-safe** and can
+ * be safely invoked from concurrent coroutines without external synchronization.
+ */
+public interface Job : CoroutineContext.Element {
+ /**
+ * Key for [Job] instance in the coroutine context.
+ */
+ public companion object Key : CoroutineContext.Key<Job> {
+ /**
+ * Creates a new job object in _active_ state.
+ * It is optionally a child of a [parent] job.
+ * @suppress **Deprecated**
+ */
+ @Deprecated("Replaced with top-level function", level = DeprecationLevel.HIDDEN)
+ public operator fun invoke(parent: Job? = null): Job = Job(parent)
+
+ init {
+ /*
+ * Here we make sure that CoroutineExceptionHandler is always initialized in advance, so
+ * that if a coroutine fails due to StackOverflowError we don't fail to report this error
+ * trying to initialize CoroutineExceptionHandler
+ */
+ CoroutineExceptionHandler
+ }
+ }
+
+ // ------------ state query ------------
+
+ /**
+ * Returns `true` when this job is active -- it was already started and has not completed or cancelled yet.
+ * The job that is waiting for its [children][attachChild] to complete is still considered to be active if it
+ * was not cancelled.
+ */
+ public val isActive: Boolean
+
+ /**
+ * Returns `true` when this job has completed for any reason. A job that was cancelled and has
+ * finished its execution is also considered complete. Job becomes complete only after
+ * all its [children][attachChild] complete.
+ */
+ public val isCompleted: Boolean
+
+ /**
+ * Returns `true` if this job was [cancelled][cancel]. In the general case, it does not imply that the
+ * job has already [completed][isCompleted] (it may still be cancelling whatever it was doing).
+ */
+ public val isCancelled: Boolean
+
+ /**
+ * Returns [CancellationException] that signals the completion of this job.
+ *
+ * It returns the original [cancel] cause if it is an instance of [CancellationException] or
+ * an instance of [JobCancellationException] if this job was cancelled with a cause of
+ * different type, was cancelled without a cause or had completed normally.
+ *
+ * This function throws [IllegalStateException] when invoked for an job that has not
+ * [completed][isCompleted] nor [cancelled][isCancelled] yet.
+ *
+ * The [cancellable][suspendCancellableCoroutine] suspending functions throw this exception
+ * when trying to suspend in the context of this job.
+ */
+ public fun getCancellationException(): CancellationException
+
+ /**
+ * @suppress **Deprecated**: Renamed to [getCancellationException]
+ */
+ @Deprecated("Renamed to getCancellationException", replaceWith = ReplaceWith("getCancellationException()"))
+ public fun getCompletionException(): Throwable =
+ getCancellationException()
+
+ // ------------ state update ------------
+
+ /**
+ * Starts coroutine related to this job (if any) if it was not started yet.
+ * The result `true` if this invocation actually started coroutine or `false`
+ * if it was already started or completed.
+ */
+ public fun start(): Boolean
+
+ /**
+ * Cancels this job with an optional cancellation [cause]. The result is `true` if this job was
+ * cancelled as a result of this invocation and `false` otherwise
+ * (if it was already _completed_ or if it is [NonCancellable]).
+ * Repeated invocations of this function have no effect and always produce `false`.
+ *
+ * When cancellation has a clear reason in the code, an instance of [CancellationException] should be created
+ * at the corresponding original cancellation site and passed into this method to aid in debugging by providing
+ * both the context of cancellation and text description of the reason.
+ */
+ public fun cancel(cause: Throwable? = null): Boolean
+
+ // ------------ parent-child ------------
+
+ /**
+ * Attaches child job so that this job becomes its parent and
+ * returns a handle that should be used to detach it.
+ *
+ * A parent-child relation has the following effect:
+ * * Cancellation of parent with [cancel] immediately cancels all its children with the same cause.
+ * * Parent cannot complete until all its children are complete. Parent waits for all its children to
+ * complete first in _completing_ or _cancelling_ state.
+ *
+ * A child must store the resulting [DisposableHandle] and [dispose][DisposableHandle.dispose] the attachment
+ * to its parent on its own completion.
+ *
+ * Coroutine builders and job factory functions that accept `parent` [CoroutineContext] parameter
+ * lookup a [Job] instance in the parent context and use this function to attach themselves as a child.
+ * They also store a reference to the resulting [DisposableHandle] and dispose a handle when they complete.
+ */
+ public fun attachChild(child: Job): DisposableHandle
+
+ /**
+ * Cancels all [children][attachChild] jobs of this coroutine with the given [cause]. Unlike [cancel],
+ * the state of this job itself is not affected.
+ */
+ public fun cancelChildren(cause: Throwable? = null)
+
+ // ------------ state waiting ------------
+
+ /**
+ * Suspends coroutine until this job is complete. This invocation resumes normally (without exception)
+ * when the job is complete for any reason and the [Job] of the invoking coroutine is still [active][isActive].
+ * This function also [starts][Job.start] the corresponding coroutine if the [Job] was still in _new_ state.
+ *
+ * Note, that the job becomes complete only when all its [children][attachChild] are complete.
+ *
+ * This suspending function is cancellable and **always** checks for the cancellation of invoking coroutine's Job.
+ * If the [Job] of the invoking coroutine is cancelled or completed when this
+ * suspending function is invoked or while it is suspended, this function
+ * throws [CancellationException].
+ *
+ * In particular, it means that a parent coroutine invoking `join` on a child coroutine that was started using
+ * `launch(coroutineContext) { ... }` builder throws [CancellationException] if the child
+ * had crashed, unless a non-standard [CoroutineExceptionHandler] if installed in the context.
+ *
+ * This function can be used in [select] invocation with [onJoin] clause.
+ * Use [isCompleted] to check for completion of this job without waiting.
+ *
+ * There is [cancelAndJoin] function that combines an invocation of [cancel] and `join`.
+ */
+ public suspend fun join()
+
+ /**
+ * Clause for [select] expression of [join] suspending function that selects when the job is complete.
+ * This clause never fails, even if the job completes exceptionally.
+ */
+ public val onJoin: SelectClause0
+
+ // ------------ low-level state-notification ------------
+
+ @Deprecated(message = "For binary compatibility", level = DeprecationLevel.HIDDEN)
+ public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
+
+ @Deprecated(message = "For binary compatibility", level = DeprecationLevel.HIDDEN)
+ public fun invokeOnCompletion(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle
+
+ /**
+ * Registers handler that is **synchronously** invoked once on cancellation or completion of this job.
+ * When job is already complete, then the handler is immediately invoked
+ * with a job's cancellation cause or `null`. Otherwise, handler will be invoked once when this
+ * job is cancelled or complete.
+ *
+ * Invocation of this handler on a transition to a transient _cancelling_ state
+ * is controlled by [onCancelling] boolean parameter.
+ * The handler is invoked on invocation of [cancel] when
+ * job becomes _cancelling_ if [onCancelling] parameter is set to `true`. However,
+ * when this [Job] is not backed by a coroutine, like [CompletableDeferred] or [CancellableContinuation]
+ * (both of which do not posses a _cancelling_ state), then the value of [onCancelling] parameter is ignored.
+ *
+ * The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] the
+ * registration of this handler and release its memory if its invocation is no longer needed.
+ * There is no need to dispose the handler after completion of this job. The references to
+ * all the handlers are released when this job completes.
+ *
+ * Installed [handler] should not throw any exceptions. If it does, they will get caught,
+ * wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code.
+ *
+ * **Note**: This function is a part of internal machinery that supports parent-child hierarchies
+ * and allows for implementation of suspending functions that wait on the Job's state.
+ * This function should not be used in general application code.
+ * Implementations of `CompletionHandler` must be fast and _lock-free_.
+ */
+ public fun invokeOnCompletion(onCancelling: Boolean = false, handler: CompletionHandler): DisposableHandle
+
+ // ------------ unstable internal API ------------
+
+ /**
+ * @suppress **Error**: Operator '+' on two Job objects is meaningless.
+ * Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts.
+ * The job to the right of `+` just replaces the job the left of `+`.
+ */
+ @Suppress("DeprecatedCallableAddReplaceWith")
+ @Deprecated(message = "Operator '+' on two Job objects is meaningless. " +
+ "Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts. " +
+ "The job to the right of `+` just replaces the job the left of `+`.",
+ level = DeprecationLevel.ERROR)
+ public operator fun plus(other: Job) = other
+
+ /**
+ * Registration object for [invokeOnCompletion]. It can be used to [unregister] if needed.
+ * There is no need to unregister after completion.
+ * @suppress **Deprecated**: Replace with `DisposableHandle`
+ */
+ @Deprecated(message = "Replace with `DisposableHandle`",
+ replaceWith = ReplaceWith("DisposableHandle"))
+ public interface Registration {
+ /**
+ * Unregisters completion handler.
+ * @suppress **Deprecated**: Replace with `dispose`
+ */
+ @Deprecated(message = "Replace with `dispose`",
+ replaceWith = ReplaceWith("dispose()"))
+ public fun unregister()
+ }
+}
+
+/**
+ * Creates a new job object in an _active_ state.
+ * It is optionally a child of a [parent] job.
+ */
+@Suppress("FunctionName")
+public fun Job(parent: Job? = null): Job = JobImpl(parent)
+
+/**
+ * A handle to an allocated object that can be disposed to make it eligible for garbage collection.
+ */
+@Suppress("DEPRECATION") // todo: remove when Job.Registration is removed
+public interface DisposableHandle : Job.Registration {
+ /**
+ * Disposes the corresponding object, making it eligible for garbage collection.
+ * Repeated invocation of this function has no effect.
+ */
+ public fun dispose()
+
+ /**
+ * Unregisters completion handler.
+ * @suppress **Deprecated**: Replace with `dispose`
+ */
+ @Deprecated(message = "Replace with `dispose`",
+ replaceWith = ReplaceWith("dispose()"))
+ public override fun unregister() = dispose()
+}
+
+/**
+ * Handler for [Job.invokeOnCompletion].
+ *
+ * Installed handler should not throw any exceptions. If it does, they will get caught,
+ * wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code.
+ *
+ * **Note**: This type is a part of internal machinery that supports parent-child hierarchies
+ * and allows for implementation of suspending functions that wait on the Job's state.
+ * This type should not be used in general application code.
+ * Implementations of `CompletionHandler` must be fast and _lock-free_.
+ */
+public typealias CompletionHandler = (cause: Throwable?) -> Unit
+
+/**
+ * This exception gets thrown if an exception is caught while processing [CompletionHandler] invocation for [Job].
+ */
+public class CompletionHandlerException(message: String, cause: Throwable) : RuntimeException(message, cause)
+
+/**
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending.
+ */
+public typealias CancellationException = java.util.concurrent.CancellationException
+
+/**
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed
+ * without cause, or with a cause or exception that is not [CancellationException]
+ * (see [Job.getCancellationException]).
+ */
+public class JobCancellationException(
+ message: String,
+ cause: Throwable?,
+ /**
+ * The job that was cancelled.
+ */
+ public val job: Job
+) : CancellationException(message) {
+ init { if (cause != null) initCause(cause) }
+ override fun toString(): String = "${super.toString()}; job=$job"
+ override fun equals(other: Any?): Boolean =
+ other === this ||
+ other is JobCancellationException && other.message == message && other.job == job && other.cause == cause
+ override fun hashCode(): Int =
+ (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0)
+}
+
+/**
+ * Represents an exception in the coroutine that was not caught by it and was not expected to be thrown.
+ * This happens when coroutine is cancelled, but it completes with the different exception than its cancellation
+ * cause was.
+ */
+public class UnexpectedCoroutineException(message: String, cause: Throwable) : IllegalStateException(message, cause)
+
+/**
+ * Unregisters a specified [registration] when this job is complete.
+ *
+ * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
+ * ```
+ * invokeOnCompletion { registration.unregister() }
+ * ```
+ * @suppress: **Deprecated**: Renamed to `disposeOnCompletion`.
+ */
+@Deprecated(message = "Renamed to `disposeOnCompletion`",
+ replaceWith = ReplaceWith("disposeOnCompletion(registration)"))
+public fun Job.unregisterOnCompletion(registration: DisposableHandle): DisposableHandle =
+ invokeOnCompletion(handler = DisposeOnCompletion(this, registration))
+
+/**
+ * Disposes a specified [handle] when this job is complete.
+ *
+ * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
+ * ```
+ * invokeOnCompletion { handle.dispose() }
+ * ```
+ */
+public fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle =
+ invokeOnCompletion(handler = DisposeOnCompletion(this, handle))
+
+/**
+ * Cancels a specified [future] when this job is complete.
+ *
+ * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
+ * ```
+ * invokeOnCompletion { future.cancel(false) }
+ * ```
+ */
+public fun Job.cancelFutureOnCompletion(future: Future<*>): DisposableHandle =
+ invokeOnCompletion(handler = CancelFutureOnCompletion(this, future))
+
+/**
+ * Cancels the job and suspends invoking coroutine until the cancelled job is complete.
+ *
+ * This suspending function is cancellable and **always** checks for the cancellation of invoking coroutine's Job.
+ * If the [Job] of the invoking coroutine is cancelled or completed when this
+ * suspending function is invoked or while it is suspended, this function
+ * throws [CancellationException].
+ *
+ * In particular, it means that a parent coroutine invoking `cancelAndJoin` on a child coroutine that was started using
+ * `launch(coroutineContext) { ... }` builder throws [CancellationException] if the child
+ * had crashed, unless a non-standard [CoroutineExceptionHandler] if installed in the context.
+ *
+ * This is a shortcut for the invocation of [cancel][Job.cancel] followed by [join][Job.join].
+ */
+public suspend fun Job.cancelAndJoin() {
+ cancel()
+ return join()
+}
+
+/**
+ * Cancels [Job] of this context with an optional cancellation [cause]. The result is `true` if the job was
+ * cancelled as a result of this invocation and `false` if there is no job in the context or if it was already
+ * cancelled or completed. See [Job.cancel] for details.
+ */
+public fun CoroutineContext.cancel(cause: Throwable? = null): Boolean =
+ this[Job]?.cancel(cause) ?: false
+
+/**
+ * Cancels all children of the [Job] in this context with an optional cancellation [cause].
+ * It does not do anything if there is no job in the context or it has no children.
+ * See [Job.cancelChildren] for details.
+ */
+public fun CoroutineContext.cancelChildren(cause: Throwable? = null) {
+ this[Job]?.cancelChildren(cause)
+}
+
+/**
+ * @suppress **Deprecated**: `join` is now a member function of `Job`.
+ */
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "DeprecatedCallableAddReplaceWith")
+@Deprecated(message = "`join` is now a member function of `Job`")
+public suspend fun Job.join() = this.join()
+
+/**
+ * No-op implementation of [Job.Registration].
+ */
+@Deprecated(message = "Replace with `NonDisposableHandle`",
+ replaceWith = ReplaceWith("NonDisposableHandle"))
+@Suppress("unused")
+typealias EmptyRegistration = NonDisposableHandle
+
+/**
+ * No-op implementation of [DisposableHandle].
+ */
+public object NonDisposableHandle : DisposableHandle {
+ /** Does not do anything. */
+ override fun dispose() {}
+
+ /** Returns "NonDisposableHandle" string. */
+ override fun toString(): String = "NonDisposableHandle"
+}
+
+// --------------- helper classes to simplify job implementation
+
+/**
+ * A concrete implementation of [Job]. It is optionally a child to a parent job.
+ * This job is cancelled when the parent is complete, but not vise-versa.
+ *
+ * This is an open class designed for extension by more specific classes that might augment the
+ * state and mare store addition state information for completed jobs, like their result values.
+ *
+ * @param active when `true` the job is created in _active_ state, when `false` in _new_ state. See [Job] for details.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause1<Any?> {
+ override val key: CoroutineContext.Key<*> get() = Job
+
+ /*
+ === Internal states ===
+
+ name state class public state description
+ ------ ------------ ------------ -----------
+ EMPTY_N EmptyNew : New no listeners
+ EMPTY_A EmptyActive : Active no listeners
+ SINGLE JobNode : Active a single listener
+ SINGLE+ JobNode : Active a single listener + NodeList added as its next
+ LIST_N NodeList : New a list of listeners (promoted once, does not got back to EmptyNew)
+ LIST_A NodeList : Active a list of listeners (promoted once, does not got back to JobNode/EmptyActive)
+ COMPLETING Finishing : Completing has a list of listeners (promoted once from LIST_*)
+ CANCELLING Finishing : Cancelling has a list of listeners (promoted once from LIST_*)
+ FINAL_C Cancelled : Cancelled cancelled (final state)
+ FINAL_F Failed : Completed failed for other reason (final state)
+ FINAL_R <any> : Completed produced some result
+
+ === Transitions ===
+
+ New states Active states Inactive states
+
+ +---------+ +---------+ }
+ | EMPTY_N | --+-> | EMPTY_A | ----+ } Empty states
+ +---------+ | +---------+ | }
+ | | | ^ | +----------+
+ | | | | +--> | FINAL_* |
+ | | V | | +----------+
+ | | +---------+ | }
+ | | | SINGLE | ----+ } JobNode states
+ | | +---------+ | }
+ | | | | }
+ | | V | }
+ | | +---------+ | }
+ | +-- | SINGLE+ | ----+ }
+ | +---------+ | }
+ | | |
+ V V |
+ +---------+ +---------+ | }
+ | LIST_N | ----> | LIST_A | ----+ } NodeList states
+ +---------+ +---------+ | }
+ | | | | |
+ | | +--------+ | |
+ | | | V |
+ | | | +------------+ | +------------+ }
+ | +-------> | COMPLETING | --+-- | CANCELLING | } Finishing states
+ | | +------------+ +------------+ }
+ | | | ^
+ | | | |
+ +--------+---------+--------------------+
+
+
+ This state machine and its transition matrix are optimized for the common case when job is created in active
+ state (EMPTY_A) and at most one completion listener is added to it during its life-time.
+
+ Note, that the actual `_state` variable can also be a reference to atomic operation descriptor `OpDescriptor`
+ */
+
+ // Note: use shared objects while we have no listeners
+ private val _state = atomic<Any?>(if (active) EmptyActive else EmptyNew)
+
+ @Volatile
+ private var parentHandle: DisposableHandle? = null
+
+ // ------------ initialization ------------
+
+ /**
+ * Initializes parent job.
+ * It shall be invoked at most once after construction after all other initialization.
+ */
+ public fun initParentJob(parent: Job?) {
+ check(parentHandle == null)
+ if (parent == null) {
+ parentHandle = NonDisposableHandle
+ return
+ }
+ parent.start() // make sure the parent is started
+ val handle = parent.attachChild(this)
+ parentHandle = handle
+ // now check our state _after_ registering (see updateState order of actions)
+ if (isCompleted) handle.dispose()
+ }
+
+ // ------------ state query ------------
+
+ /**
+ * Returns current state of this job.
+ */
+ protected val state: Any? get() {
+ _state.loop { state -> // helper loop on state (complete in-progress atomic operations)
+ if (state !is OpDescriptor) return state
+ state.perform(this)
+ }
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected inline fun loopOnState(block: (Any?) -> Unit): Nothing {
+ while (true) {
+ block(state)
+ }
+ }
+
+ public final override val isActive: Boolean get() {
+ val state = this.state
+ return state is Incomplete && state.isActive
+ }
+
+ public final override val isCompleted: Boolean get() = state !is Incomplete
+
+ public final override val isCancelled: Boolean get() {
+ val state = this.state
+ return state is Cancelled || (state is Finishing && state.cancelled != null)
+ }
+
+ // ------------ state update ------------
+
+ /**
+ * Updates current [state] of this job. Returns `false` if current state is not equal to expected.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal fun updateState(expect: Incomplete, proposedUpdate: Any?, mode: Int): Boolean {
+ val update = coerceProposedUpdate(expect, proposedUpdate)
+ if (!tryUpdateState(expect, update)) return false
+ completeUpdateState(expect, update, mode)
+ // if an exceptional completion was suppressed (because cancellation was in progress), then report it separately
+ if (proposedUpdate is CompletedExceptionally && proposedUpdate.cause != null && !incorporatedCause(update, proposedUpdate.cause)) {
+ handleException(UnexpectedCoroutineException("Unexpected exception while cancellation is in progress; job=$this", proposedUpdate.cause))
+ }
+ return true
+ }
+
+ /**
+ * Checks if the cause that was proposed for state update is consistent with the resulting updated state
+ * and not exception information was lost. The key observation here is that [getCancellationException] wraps
+ * exceptions that are not [CancellationException] into an instance of [JobCancellationException] and we allow
+ * that [JobCancellationException] to be unwrapped again when it reaches the coroutine that was cancelled.
+ *
+ * NOTE: equality comparison of exceptions is performed here by design, see equals of JobCancellationException
+ */
+ private fun incorporatedCause(update: Any?, proposedCause: Throwable) =
+ update is CompletedExceptionally && update.exception.let { ex ->
+ ex == proposedCause || proposedCause is JobCancellationException && ex == proposedCause.cause
+ }
+
+ // when Job is in Cancelling state, it can only be promoted to Cancelled state with the same cause
+ // however, null cause can be replaced with more specific JobCancellationException (that contains better stack trace)
+ private fun coerceProposedUpdate(expect: Incomplete, proposedUpdate: Any?): Any? =
+ if (expect is Finishing && expect.cancelled != null && !correspondinglyCancelled(expect.cancelled, proposedUpdate))
+ expect.cancelled else proposedUpdate
+
+ private fun correspondinglyCancelled(cancelled: Cancelled, proposedUpdate: Any?): Boolean {
+ if (proposedUpdate !is Cancelled) return false
+ return proposedUpdate.cause === cancelled.cause ||
+ proposedUpdate.cause is JobCancellationException && cancelled.cause == null
+ }
+
+ /**
+ * Tries to initiate update of the current [state] of this job.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal fun tryUpdateState(expect: Incomplete, update: Any?): Boolean {
+ require(update !is Incomplete) // only incomplete -> completed transition is allowed
+ if (!_state.compareAndSet(expect, update)) return false
+ // Unregister from parent job
+ parentHandle?.dispose() // volatile read parentHandle _after_ state was updated
+ return true // continues in completeUpdateState
+ }
+
+ /**
+ * Completes update of the current [state] of this job.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal fun completeUpdateState(expect: Incomplete, update: Any?, mode: Int) {
+ // Invoke completion handlers
+ val exceptionally = update as? CompletedExceptionally
+ val cause = exceptionally?.cause
+ if (expect is JobNode<*>) { // SINGLE/SINGLE+ state -- one completion handler (common case)
+ try {
+ expect.invoke(cause)
+ } catch (ex: Throwable) {
+ handleException(CompletionHandlerException("Exception in completion handler $expect for $this", ex))
+ }
+ } else {
+ expect.list?.notifyCompletion(cause)
+ }
+ // Do overridable processing after completion handlers
+ if (!expect.isCancelling) onCancellation(exceptionally) // only notify when was not cancelling before
+ afterCompletion(update, mode)
+ }
+
+ private inline fun <reified T: JobNode<*>> notifyHandlers(list: NodeList, cause: Throwable?) {
+ var exception: Throwable? = null
+ list.forEach<T> { node ->
+ try {
+ node.invoke(cause)
+ } catch (ex: Throwable) {
+ exception?.apply { addSuppressed(ex) } ?: run {
+ exception = CompletionHandlerException("Exception in completion handler $node for $this", ex)
+ }
+ }
+ }
+ exception?.let { handleException(it) }
+ }
+
+ private fun NodeList.notifyCompletion(cause: Throwable?) =
+ notifyHandlers<JobNode<*>>(this, cause)
+
+ private fun notifyCancellation(list: NodeList, cause: Throwable?) =
+ notifyHandlers<JobCancellationNode<*>>(list, cause)
+
+ public final override fun start(): Boolean {
+ loopOnState { state ->
+ when (startInternal(state)) {
+ FALSE -> return false
+ TRUE -> return true
+ }
+ }
+ }
+
+ // returns: RETRY/FALSE/TRUE:
+ // FALSE when not new,
+ // TRUE when started
+ // RETRY when need to retry
+ private fun startInternal(state: Any?): Int {
+ when (state) {
+ is Empty -> { // EMPTY_X state -- no completion handlers
+ if (state.isActive) return FALSE // already active
+ if (!_state.compareAndSet(state, EmptyActive)) return RETRY
+ onStart()
+ return TRUE
+ }
+ is NodeList -> { // LIST -- a list of completion handlers (either new or active)
+ return state.tryMakeActive().also { result ->
+ if (result == TRUE) onStart()
+ }
+ }
+ else -> return FALSE // not a new state
+ }
+ }
+
+ /**
+ * Override to provide the actual [start] action.
+ */
+ protected open fun onStart() {}
+
+ public final override fun getCancellationException(): CancellationException {
+ val state = this.state
+ return when {
+ state is Finishing && state.cancelled != null ->
+ state.cancelled.exception.toCancellationException("Job is being cancelled")
+ state is Incomplete ->
+ error("Job was not completed or cancelled yet: $this")
+ state is CompletedExceptionally ->
+ state.exception.toCancellationException("Job has failed")
+ else -> JobCancellationException("Job has completed normally", null, this)
+ }
+ }
+
+ private fun Throwable.toCancellationException(message: String): CancellationException =
+ this as? CancellationException ?: JobCancellationException(message, this, this@JobSupport)
+
+ /**
+ * Returns the cause that signals the completion of this job -- it returns the original
+ * [cancel] cause or **`null` if this job had completed
+ * normally or was cancelled without a cause**. This function throws
+ * [IllegalStateException] when invoked for an job that has not [completed][isCompleted] nor
+ * [isCancelled] yet.
+ */
+ protected fun getCompletionCause(): Throwable? {
+ val state = this.state
+ return when {
+ state is Finishing && state.cancelled != null -> state.cancelled.cause
+ state is Incomplete -> error("Job was not completed or cancelled yet")
+ state is CompletedExceptionally -> state.cause
+ else -> null
+ }
+ }
+
+ @Suppress("OverridingDeprecatedMember")
+ public final override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle =
+ installHandler(handler, onCancelling = false)
+
+ @Suppress("OverridingDeprecatedMember")
+ public final override fun invokeOnCompletion(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle =
+ installHandler(handler, onCancelling = onCancelling && hasCancellingState)
+
+ public final override fun invokeOnCompletion(onCancelling: Boolean, handler: CompletionHandler): DisposableHandle =
+ installHandler(handler, onCancelling = onCancelling && hasCancellingState)
+
+ private fun installHandler(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle {
+ var nodeCache: JobNode<*>? = null
+ loopOnState { state ->
+ when (state) {
+ is Empty -> { // EMPTY_X state -- no completion handlers
+ if (state.isActive) {
+ // try move to SINGLE state
+ val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
+ if (_state.compareAndSet(state, node)) return node
+ } else
+ promoteEmptyToNodeList(state) // that way we can add listener for non-active coroutine
+ }
+ is Incomplete -> {
+ val list = state.list
+ if (list == null) { // SINGLE/SINGLE+
+ promoteSingleToNodeList(state as JobNode<*>)
+ } else {
+ if (state is Finishing && state.cancelled != null && onCancelling) {
+ // installing cancellation handler on job that is being cancelled
+ handler((state as? CompletedExceptionally)?.exception)
+ return NonDisposableHandle
+ }
+ val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
+ if (addLastAtomic(state, list, node)) return node
+ }
+ }
+ else -> { // is complete
+ handler((state as? CompletedExceptionally)?.exception)
+ return NonDisposableHandle
+ }
+ }
+ }
+ }
+
+ private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode<*> =
+ if (onCancelling)
+ (handler as? JobCancellationNode<*>)?.also { require(it.job === this) }
+ ?: InvokeOnCancellation(this, handler)
+ else
+ (handler as? JobNode<*>)?.also { require(it.job === this && (!hasCancellingState || it !is JobCancellationNode)) }
+ ?: InvokeOnCompletion(this, handler)
+
+
+ private fun addLastAtomic(expect: Any, list: NodeList, node: JobNode<*>) =
+ list.addLastIf(node) { this.state === expect }
+
+ private fun promoteEmptyToNodeList(state: Empty) {
+ // try to promote it to list in new state
+ _state.compareAndSet(state, NodeList(state.isActive))
+ }
+
+ private fun promoteSingleToNodeList(state: JobNode<*>) {
+ // try to promote it to list (SINGLE+ state)
+ state.addOneIfEmpty(NodeList(active = true))
+ // it must be in SINGLE+ state or state has changed (node could have need removed from state)
+ val list = state.next // either NodeList or somebody else won the race, updated state
+ // just attempt converting it to list if state is still the same, then we'll continue lock-free loop
+ _state.compareAndSet(state, list)
+ }
+
+ final override suspend fun join() {
+ if (!joinInternal()) { // fast-path no wait
+ return suspendCoroutineOrReturn { cont ->
+ cont.context.checkCompletion()
+ Unit // do not suspend
+ }
+ }
+ return joinSuspend() // slow-path wait
+ }
+
+ private fun joinInternal(): Boolean {
+ loopOnState { state ->
+ if (state !is Incomplete) return false // not active anymore (complete) -- no need to wait
+ if (startInternal(state) >= 0) return true // wait unless need to retry
+ }
+ }
+
+ private suspend fun joinSuspend() = suspendCancellableCoroutine<Unit> { cont ->
+ cont.disposeOnCompletion(invokeOnCompletion(handler = ResumeOnCompletion(this, cont)))
+ }
+
+ final override val onJoin: SelectClause0
+ get() = this
+
+ // registerSelectJoin
+ final override fun <R> registerSelectClause0(select: SelectInstance<R>, block: suspend () -> R) {
+ // fast-path -- check state and select/return if needed
+ loopOnState { state ->
+ if (select.isSelected) return
+ if (state !is Incomplete) {
+ // already complete -- select result
+ if (select.trySelect(null)) {
+ select.completion.context.checkCompletion() // always check for our completion
+ block.startCoroutineUndispatched(select.completion)
+ }
+ return
+ }
+ if (startInternal(state) == 0) {
+ // slow-path -- register waiter for completion
+ select.disposeOnSelect(invokeOnCompletion(handler = SelectJoinOnCompletion(this, select, block)))
+ return
+ }
+ }
+ }
+
+ internal fun removeNode(node: JobNode<*>) {
+ // remove logic depends on the state of the job
+ loopOnState { state ->
+ when (state) {
+ is JobNode<*> -> { // SINGE/SINGLE+ state -- one completion handler
+ if (state !== node) return // a different job node --> we were already removed
+ // try remove and revert back to empty state
+ if (_state.compareAndSet(state, EmptyActive)) return
+ }
+ is Incomplete -> { // may have a list of completion handlers
+ // remove node from the list if there is a list
+ if (state.list != null) node.remove()
+ return
+ }
+ else -> return // it is complete and does not have any completion handlers
+ }
+ }
+ }
+
+ protected open val hasCancellingState: Boolean get() = false
+
+ public final override fun cancel(cause: Throwable?): Boolean =
+ if (hasCancellingState)
+ makeCancelling(cause) else
+ makeCancelled(cause)
+
+ // we will be dispatching coroutine to process its cancellation exception, so there is no need for
+ // an extra check for Job status in MODE_CANCELLABLE
+ private fun updateStateCancelled(state: Incomplete, cause: Throwable?) =
+ updateState(state, Cancelled(this, cause), mode = MODE_ATOMIC_DEFAULT)
+
+ // transitions to Cancelled state
+ private fun makeCancelled(cause: Throwable?): Boolean {
+ loopOnState { state ->
+ if (state !is Incomplete) return false // quit if already complete
+ if (updateStateCancelled(state, cause)) return true
+ }
+ }
+
+ // transitions to Cancelling state
+ private fun makeCancelling(cause: Throwable?): Boolean {
+ loopOnState { state ->
+ when (state) {
+ is Empty -> { // EMPTY_X state -- no completion handlers
+ if (state.isActive) {
+ promoteEmptyToNodeList(state) // this way can wrap it into Cancelling on next pass
+ } else {
+ // cancelling a non-started coroutine makes it immediately cancelled
+ // (and we have no listeners to notify which makes it very simple)
+ if (updateStateCancelled(state, cause)) return true
+ }
+ }
+ is JobNode<*> -> { // SINGLE/SINGLE+ state -- one completion handler
+ promoteSingleToNodeList(state)
+ }
+ is NodeList -> { // LIST -- a list of completion handlers (either new or active)
+ if (state.isActive) {
+ if (tryMakeCancelling(state, state.list, cause)) return true
+ } else {
+ // cancelling a non-started coroutine makes it immediately cancelled
+ if (updateStateCancelled(state, cause))
+ return true
+ }
+ }
+ is Finishing -> { // Completing/Cancelling the job, may cancel
+ if (state.cancelled != null) return false // already cancelling
+ if (tryMakeCancelling(state, state.list, cause)) return true
+ }
+ else -> { // is inactive
+ return false
+ }
+ }
+ }
+ }
+
+ // try make expected state in cancelling on the condition that we're still in this state
+ private fun tryMakeCancelling(expect: Incomplete, list: NodeList, cause: Throwable?): Boolean {
+ val cancelled = Cancelled(this, cause)
+ if (!_state.compareAndSet(expect, Finishing(list, cancelled, false))) return false
+ notifyCancellation(list, cause)
+ onCancellation(cancelled)
+ return true
+ }
+
+ /**
+ * Returns:
+ * * `true` if state was updated to completed/cancelled;
+ * * `false` if made completing or it is cancelling and is waiting for children.
+ *
+ * @throws IllegalStateException if job is already complete or completing
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal fun makeCompleting(proposedUpdate: Any?, mode: Int): Boolean {
+ loopOnState { state ->
+ if (state !is Incomplete)
+ throw IllegalStateException("Job $this is already complete, but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull)
+ if (state is Finishing && state.completing)
+ throw IllegalStateException("Job $this is already completing, but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull)
+ val waitChild: Child = firstChild(state) ?: // or else complete immediately
+ if (updateState(state, proposedUpdate, mode)) return true else return@loopOnState
+ // switch to completing state
+ if (state is JobNode<*>) {
+ // must promote to list to make completing & retry
+ promoteSingleToNodeList(state)
+ } else {
+ val completing = Finishing(state.list!!, (state as? Finishing)?.cancelled, true)
+ if (_state.compareAndSet(state, completing)) {
+ waitForChild(waitChild, proposedUpdate)
+ return false
+ }
+ }
+ }
+ }
+
+ private val Any?.exceptionOrNull: Throwable?
+ get() = (this as? CompletedExceptionally)?.exception
+
+ private fun firstChild(state: Incomplete) =
+ state as? Child ?: state.list?.nextChild()
+
+ private fun waitForChild(waitChild: Child, proposedUpdate: Any?) {
+ waitChild.child.invokeOnCompletion(handler = ChildCompletion(this, waitChild, proposedUpdate))
+ }
+
+ internal fun continueCompleting(lastChild: Child, proposedUpdate: Any?) {
+ loopOnState { state ->
+ if (state !is Finishing)
+ throw IllegalStateException("Job $this is found in expected state while completing with $proposedUpdate", proposedUpdate.exceptionOrNull)
+ // figure out if we need to wait for next child
+ val waitChild = lastChild.nextChild() ?: // or else no more children
+ if (updateState(state, proposedUpdate, MODE_ATOMIC_DEFAULT)) return else return@loopOnState
+ // wait for next child
+ waitForChild(waitChild, proposedUpdate)
+ return
+ }
+ }
+
+ private fun LockFreeLinkedListNode.nextChild(): Child? {
+ var cur = this
+ while (cur.isRemoved) cur = cur.prev.unwrap() // rollback to prev non-removed (or list head)
+ while (true) {
+ cur = cur.next.unwrap()
+ if (cur.isRemoved) continue
+ if (cur is Child) return cur
+ if (cur is NodeList) return null // checked all -- no more children
+ }
+ }
+
+ override fun attachChild(child: Job): DisposableHandle =
+ invokeOnCompletion(onCancelling = true, handler = Child(this, child))
+
+ public override fun cancelChildren(cause: Throwable?) {
+ val state = this.state
+ when (state) {
+ is Child -> state.child.cancel(cause)
+ is Incomplete -> state.list?.cancelChildrenList(cause)
+ }
+ }
+
+ private fun NodeList.cancelChildrenList(cause: Throwable?) {
+ forEach<Child> { it.child.cancel(cause) }
+ }
+
+ /**
+ * Override to process any exceptions that were encountered while invoking completion handlers
+ * installed via [invokeOnCompletion].
+ */
+ protected open fun handleException(exception: Throwable) {
+ throw exception
+ }
+
+ /**
+ * It is invoked once when job is cancelled or is completed, similarly to [invokeOnCompletion] with
+ * `onCancelling` set to `true`.
+ * @param exceptionally not null when the the job was cancelled or completed exceptionally,
+ * null when it has completed normally.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun onCancellation(exceptionally: CompletedExceptionally?) {}
+
+ /**
+ * Override for post-completion actions that need to do something with the state.
+ * @param mode completion mode.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun afterCompletion(state: Any?, mode: Int) {}
+
+ // for nicer debugging
+ override final fun toString(): String =
+ "${nameString()}{${stateString()}}@${Integer.toHexString(System.identityHashCode(this))}"
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ protected open fun nameString(): String = this::class.java.simpleName
+
+ private fun stateString(): String {
+ val state = this.state
+ return when (state) {
+ is Finishing -> buildString {
+ if (state.cancelled != null) append("Cancelling")
+ if (state.completing) append("Completing")
+ }
+ is Incomplete -> if (state.isActive) "Active" else "New"
+ is Cancelled -> "Cancelled"
+ is CompletedExceptionally -> "CompletedExceptionally"
+ else -> "Completed"
+ }
+ }
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal interface Incomplete {
+ val isActive: Boolean
+ val list: NodeList? // is null only for Empty and JobNode incomplete state objects
+ }
+
+ // Cancelling or Completing
+ private class Finishing(
+ override val list: NodeList,
+ @JvmField val cancelled: Cancelled?, /* != null when cancelling */
+ @JvmField val completing: Boolean /* true when completing */
+ ) : Incomplete {
+ override val isActive: Boolean get() = cancelled == null
+ }
+
+ private val Incomplete.isCancelling: Boolean
+ get() = this is Finishing && cancelled != null
+
+ /**
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ internal class NodeList(
+ active: Boolean
+ ) : LockFreeLinkedListHead(), Incomplete {
+ private val _active = atomic(if (active) 1 else 0)
+
+ override val isActive: Boolean get() = _active.value != 0
+ override val list: NodeList get() = this
+
+ fun tryMakeActive(): Int {
+ if (_active.value != 0) return FALSE
+ if (_active.compareAndSet(0, 1)) return RETRY
+ return TRUE
+ }
+
+ override fun toString(): String = buildString {
+ append("List")
+ append(if (isActive) "{Active}" else "{New}")
+ append("[")
+ var first = true
+ this@NodeList.forEach<JobNode<*>> { node ->
+ if (first) first = false else append(", ")
+ append(node)
+ }
+ append("]")
+ }
+ }
+
+ /**
+ * Class for a [state] of a job that had completed exceptionally, including cancellation.
+ *
+ * @param cause the exceptional completion cause. If `cause` is null, then an exception is
+ * if created via [createException] on first get from [exception] property.
+ * @param allowNullCause if `null` cause is allowed.
+ */
+ public open class CompletedExceptionally protected constructor(
+ public @JvmField val cause: Throwable?,
+ allowNullCause: Boolean
+ ) {
+ /**
+ * Creates exceptionally completed state.
+ * @param cause the exceptional completion cause.
+ */
+ public constructor(cause: Throwable) : this(cause, false)
+
+ @Volatile
+ private var _exception: Throwable? = cause // will materialize JobCancellationException on first need
+
+ init {
+ require(allowNullCause || cause != null) { "Null cause is not allowed" }
+ }
+
+ /**
+ * Returns completion exception.
+ */
+ public val exception: Throwable get() =
+ _exception ?: // atomic read volatile var or else create new
+ createException().also { _exception = it }
+
+ protected open fun createException(): Throwable = error("Completion exception was not specified")
+
+ override fun toString(): String = "${this::class.java.simpleName}[$exception]"
+ }
+
+ /**
+ * A specific subclass of [CompletedExceptionally] for cancelled jobs.
+ *
+ * @param job the job that was cancelled.
+ * @param cause the exceptional completion cause. If `cause` is null, then a [JobCancellationException]
+ * if created on first get from [exception] property.
+ */
+ public class Cancelled(
+ private val job: Job,
+ cause: Throwable?
+ ) : CompletedExceptionally(cause, true) {
+ override fun createException(): Throwable = JobCancellationException("Job was cancelled normally", null, job)
+ }
+
+ /*
+ * =================================================================================================
+ * This is ready-to-use implementation for Deferred interface.
+ * However, it is not type-safe. Conceptually it just exposes the value of the underlying
+ * completed state as `Any?`
+ * =================================================================================================
+ */
+
+ public val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally
+
+ public fun getCompletionExceptionOrNull(): Throwable? {
+ val state = this.state
+ check(state !is Incomplete) { "This job has not completed yet" }
+ return state.exceptionOrNull
+ }
+
+ protected fun getCompletedInternal(): Any? {
+ val state = this.state
+ check(state !is Incomplete) { "This job has not completed yet" }
+ if (state is CompletedExceptionally) throw state.exception
+ return state
+ }
+
+ protected suspend fun awaitInternal(): Any? {
+ // fast-path -- check state (avoid extra object creation)
+ while(true) { // lock-free loop on state
+ val state = this.state
+ if (state !is Incomplete) {
+ // already complete -- just return result
+ if (state is CompletedExceptionally) throw state.exception
+ return state
+
+ }
+ if (startInternal(state) >= 0) break // break unless needs to retry
+ }
+ return awaitSuspend() // slow-path
+ }
+
+ private suspend fun awaitSuspend(): Any? = suspendCancellableCoroutine { cont ->
+ cont.disposeOnCompletion(invokeOnCompletion {
+ val state = this.state
+ check(state !is Incomplete)
+ if (state is CompletedExceptionally)
+ cont.resumeWithException(state.exception)
+ else
+ cont.resume(state)
+ })
+ }
+
+ // registerSelectAwaitInternal
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (Any?) -> R) {
+ // fast-path -- check state and select/return if needed
+ loopOnState { state ->
+ if (select.isSelected) return
+ if (state !is Incomplete) {
+ // already complete -- select result
+ if (select.trySelect(null)) {
+ if (state is CompletedExceptionally)
+ select.resumeSelectCancellableWithException(state.exception)
+ else
+ block.startCoroutineUndispatched(state, select.completion)
+ }
+ return
+ }
+ if (startInternal(state) == 0) {
+ // slow-path -- register waiter for completion
+ select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(this, select, block)))
+ return
+ }
+ }
+ }
+
+ internal fun <R> selectAwaitCompletion(select: SelectInstance<R>, block: suspend (Any?) -> R) {
+ val state = this.state
+ // Note: await is non-atomic (can be cancelled while dispatched)
+ if (state is CompletedExceptionally)
+ select.resumeSelectCancellableWithException(state.exception)
+ else
+ block.startCoroutineCancellable(state, select.completion)
+ }
+}
+
+private const val RETRY = -1
+private const val FALSE = 0
+private const val TRUE = 1
+
+@Suppress("PrivatePropertyName")
+private val EmptyNew = Empty(false)
+@Suppress("PrivatePropertyName")
+private val EmptyActive = Empty(true)
+
+private class Empty(override val isActive: Boolean) : JobSupport.Incomplete {
+ override val list: JobSupport.NodeList? get() = null
+ override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}"
+}
+
+private class JobImpl(parent: Job? = null) : JobSupport(true) {
+ init { initParentJob(parent) }
+}
+
+// -------- invokeOnCompletion nodes
+
+internal abstract class JobNode<out J : Job>(
+ @JvmField val job: J
+) : LockFreeLinkedListNode(), DisposableHandle, CompletionHandler, JobSupport.Incomplete {
+ final override val isActive: Boolean get() = true
+ final override val list: JobSupport.NodeList? get() = null
+ final override fun dispose() = (job as JobSupport).removeNode(this)
+ override abstract fun invoke(reason: Throwable?)
+}
+
+private class InvokeOnCompletion(
+ job: Job,
+ private val handler: CompletionHandler
+) : JobNode<Job>(job) {
+ override fun invoke(reason: Throwable?) = handler.invoke(reason)
+ override fun toString() = "InvokeOnCompletion[${handler::class.java.name}@${Integer.toHexString(System.identityHashCode(handler))}]"
+}
+
+private class ResumeOnCompletion(
+ job: Job,
+ private val continuation: Continuation<Unit>
+) : JobNode<Job>(job) {
+ override fun invoke(reason: Throwable?) = continuation.resume(Unit)
+ override fun toString() = "ResumeOnCompletion[$continuation]"
+}
+
+internal class DisposeOnCompletion(
+ job: Job,
+ private val handle: DisposableHandle
+) : JobNode<Job>(job) {
+ override fun invoke(reason: Throwable?) = handle.dispose()
+ override fun toString(): String = "DisposeOnCompletion[$handle]"
+}
+
+private class CancelFutureOnCompletion(
+ job: Job,
+ private val future: Future<*>
+) : JobNode<Job>(job) {
+ override fun invoke(reason: Throwable?) {
+ // Don't interrupt when cancelling future on completion, because no one is going to reset this
+ // interruption flag and it will cause spurious failures elsewhere
+ future.cancel(false)
+ }
+ override fun toString() = "CancelFutureOnCompletion[$future]"
+}
+
+private class SelectJoinOnCompletion<R>(
+ job: JobSupport,
+ private val select: SelectInstance<R>,
+ private val block: suspend () -> R
+) : JobNode<JobSupport>(job) {
+ override fun invoke(reason: Throwable?) {
+ if (select.trySelect(null))
+ block.startCoroutineCancellable(select.completion)
+ }
+ override fun toString(): String = "SelectJoinOnCompletion[$select]"
+}
+
+private class SelectAwaitOnCompletion<R>(
+ job: JobSupport,
+ private val select: SelectInstance<R>,
+ private val block: suspend (Any?) -> R
+) : JobNode<JobSupport>(job) {
+ override fun invoke(reason: Throwable?) {
+ if (select.trySelect(null))
+ job.selectAwaitCompletion(select, block)
+ }
+ override fun toString(): String = "SelectAwaitOnCompletion[$select]"
+}
+
+// -------- invokeOnCancellation nodes
+
+/**
+ * Marker for node that shall be invoked on cancellation (in _cancelling_ state).
+ * **Note: may be invoked multiple times during cancellation.**
+ */
+internal abstract class JobCancellationNode<out J : Job>(job: J) : JobNode<J>(job)
+
+private class InvokeOnCancellation(
+ job: Job,
+ private val handler: CompletionHandler
+) : JobCancellationNode<Job>(job) {
+ // delegate handler shall be invoked at most once, so here is an additional flag
+ private val _invoked = atomic(0)
+ override fun invoke(reason: Throwable?) {
+ if (_invoked.compareAndSet(0, 1)) handler.invoke(reason)
+ }
+ override fun toString() = "InvokeOnCancellation[${handler::class.java.name}@${Integer.toHexString(System.identityHashCode(handler))}]"
+}
+
+internal class Child(
+ parent: JobSupport,
+ val child: Job
+) : JobCancellationNode<JobSupport>(parent) {
+ override fun invoke(reason: Throwable?) {
+ // Always materialize the actual instance of parent's completion exception and cancel child with it
+ child.cancel(job.getCancellationException())
+ }
+ override fun toString(): String = "Child[$child]"
+}
+
+private class ChildCompletion(
+ private val parent: JobSupport,
+ private val waitChild: Child,
+ private val proposedUpdate: Any?
+) : JobNode<Job>(waitChild.child) {
+ override fun invoke(reason: Throwable?) {
+ parent.continueCompleting(waitChild, proposedUpdate)
+ }
+}
+
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/LazyDeferred.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/LazyDeferred.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/LazyDeferred.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/LazyDeferred.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt
similarity index 72%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt
index c739720..e47f7b7 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt
@@ -17,7 +17,7 @@
package kotlinx.coroutines.experimental
import kotlinx.coroutines.experimental.NonCancellable.isActive
-import kotlinx.coroutines.experimental.selects.SelectInstance
+import kotlinx.coroutines.experimental.selects.SelectClause0
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
/**
@@ -49,23 +49,29 @@
throw UnsupportedOperationException("This job is always active")
}
- /**
- * Always throws [UnsupportedOperationException].
- * @suppress **This is unstable API and it is subject to change.**
- */
- override fun <R> registerSelectJoin(select: SelectInstance<R>, block: suspend () -> R) {
- throw UnsupportedOperationException("This job is always active")
- }
+ override val onJoin: SelectClause0
+ get() = throw UnsupportedOperationException("This job is always active")
/** Always throws [IllegalStateException]. */
- override fun getCompletionException(): CancellationException = throw IllegalStateException("This job is always active")
+ override fun getCancellationException(): CancellationException = throw IllegalStateException("This job is always active")
/** Always returns [NonDisposableHandle]. */
+ @Suppress("OverridingDeprecatedMember")
override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle = NonDisposableHandle
/** Always returns [NonDisposableHandle]. */
+ @Suppress("OverridingDeprecatedMember")
override fun invokeOnCompletion(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle = NonDisposableHandle
+ /** Always returns [NonDisposableHandle]. */
+ override fun invokeOnCompletion(onCancelling: Boolean, handler: CompletionHandler): DisposableHandle = NonDisposableHandle
+
/** Always returns `false`. */
override fun cancel(cause: Throwable?): Boolean = false
+
+ /** Always returns [NonDisposableHandle] and does not do anything. */
+ override fun attachChild(child: Job): DisposableHandle = NonDisposableHandle
+
+ /** Does not do anything. */
+ override fun cancelChildren(cause: Throwable?) {}
}
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ResumeMode.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ResumeMode.kt
similarity index 93%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ResumeMode.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ResumeMode.kt
index 6729978..e7f0e1f 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ResumeMode.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ResumeMode.kt
@@ -22,6 +22,7 @@
@PublishedApi internal const val MODE_CANCELLABLE = 1 // schedule cancellable dispatch for suspendCancellableCoroutine
@PublishedApi internal const val MODE_DIRECT = 2 // when the context is right just invoke the delegate continuation direct
@PublishedApi internal const val MODE_UNDISPATCHED = 3 // when the thread is right, but need to mark it with current coroutine
+@PublishedApi internal const val MODE_IGNORE = 4 // don't do anything
fun <T> Continuation<T>.resumeMode(value: T, mode: Int) {
when (mode) {
@@ -29,6 +30,7 @@
MODE_CANCELLABLE -> resumeCancellable(value)
MODE_DIRECT -> resumeDirect(value)
MODE_UNDISPATCHED -> (this as DispatchedContinuation).resumeUndispatched(value)
+ MODE_IGNORE -> {}
else -> error("Invalid mode $mode")
}
}
@@ -39,6 +41,7 @@
MODE_CANCELLABLE -> resumeCancellableWithException(exception)
MODE_DIRECT -> resumeDirectWithException(exception)
MODE_UNDISPATCHED -> (this as DispatchedContinuation).resumeUndispatchedWithException(exception)
+ MODE_IGNORE -> {}
else -> error("Invalid mode $mode")
}
}
diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
new file mode 100644
index 0000000..699c028
--- /dev/null
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental
+
+import kotlinx.coroutines.experimental.JobSupport.CompletedExceptionally
+import kotlinx.coroutines.experimental.selects.SelectBuilder
+import kotlinx.coroutines.experimental.selects.select
+import java.util.concurrent.TimeUnit
+import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.experimental.intrinsics.startCoroutineUninterceptedOrReturn
+import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
+
+/**
+ * Runs a given suspending [block] of code inside a coroutine with a specified timeout and throws
+ * [TimeoutCancellationException] if timeout was exceeded.
+ *
+ * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
+ * cancellable suspending function inside the block throws [TimeoutCancellationException].
+ * Even if the code in the block suppresses [TimeoutCancellationException], it
+ * is still thrown by `withTimeout` invocation.
+ *
+ * The sibling function that does not throw exception on timeout is [withTimeoutOrNull].
+ * Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
+ *
+ * This function delegates to [Delay.invokeOnTimeout] if the context [CoroutineDispatcher]
+ * implements [Delay] interface, otherwise it tracks time using a built-in single-threaded scheduled executor service.
+ *
+ * @param time timeout time
+ * @param unit timeout unit (milliseconds by default)
+ */
+public suspend fun <T> withTimeout(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend CoroutineScope.() -> T): T {
+ require(time >= 0) { "Timeout time $time cannot be negative" }
+ if (time <= 0L) throw CancellationException("Timed out immediately")
+ return suspendCoroutineOrReturn { cont: Continuation<T> ->
+ setupTimeout(TimeoutCoroutine(time, unit, cont), block)
+ }
+}
+
+private fun <U, T: U> setupTimeout(
+ coroutine: TimeoutCoroutine<U, T>,
+ block: suspend CoroutineScope.() -> T
+): Any? {
+ // schedule cancellation of this coroutine on time
+ val cont = coroutine.cont
+ val context = cont.context
+ coroutine.disposeOnCompletion(context.delay.invokeOnTimeout(coroutine.time, coroutine.unit, coroutine))
+ coroutine.initParentJob(context[Job])
+ // restart block using new coroutine with new job,
+ // however start it as undispatched coroutine, because we are already in the proper context
+ val result = try {
+ block.startCoroutineUninterceptedOrReturn(receiver = coroutine, completion = coroutine)
+ } catch (e: Throwable) {
+ CompletedExceptionally(e)
+ }
+ return when {
+ result == COROUTINE_SUSPENDED -> COROUTINE_SUSPENDED
+ coroutine.makeCompleting(result, MODE_IGNORE) -> {
+ if (result is CompletedExceptionally) throw result.exception else result
+ }
+ else -> COROUTINE_SUSPENDED
+ }
+}
+
+/**
+ * @suppress **Deprecated**: for binary compatibility only
+ */
+@Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+public suspend fun <T> withTimeout(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend () -> T): T =
+ withTimeout(time, unit) { block() }
+
+private open class TimeoutCoroutine<U, in T: U>(
+ @JvmField val time: Long,
+ @JvmField val unit: TimeUnit,
+ @JvmField val cont: Continuation<U>
+) : AbstractCoroutine<T>(cont.context, active = true), Runnable, Continuation<T> {
+ override val defaultResumeMode: Int get() = MODE_DIRECT
+
+ @Suppress("LeakingThis")
+ override fun run() {
+ cancel(TimeoutCancellationException(time, unit, this))
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun afterCompletion(state: Any?, mode: Int) {
+ if (state is CompletedExceptionally)
+ cont.resumeWithExceptionMode(state.exception, mode)
+ else
+ cont.resumeMode(state as T, mode)
+ }
+
+ override fun nameString(): String =
+ "${super.nameString()}($time $unit)"
+}
+
+/**
+ * Runs a given suspending block of code inside a coroutine with a specified timeout and returns
+ * `null` if this timeout was exceeded.
+ *
+ * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
+ * cancellable suspending function inside the block throws [TimeoutCancellationException].
+ * Even if the code in the block suppresses [TimeoutCancellationException], this
+ * invocation of `withTimeoutOrNull` still returns `null`.
+ *
+ * The sibling function that throws exception on timeout is [withTimeout].
+ * Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
+ *
+ * This function delegates to [Delay.invokeOnTimeout] if the context [CoroutineDispatcher]
+ * implements [Delay] interface, otherwise it tracks time using a built-in single-threaded scheduled executor service.
+ *
+ * @param time timeout time
+ * @param unit timeout unit (milliseconds by default)
+ */
+public suspend fun <T> withTimeoutOrNull(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend CoroutineScope.() -> T): T? {
+ require(time >= 0) { "Timeout time $time cannot be negative" }
+ if (time <= 0L) return null
+ return suspendCoroutineOrReturn { cont: Continuation<T?> ->
+ setupTimeout(TimeoutOrNullCoroutine(time, unit, cont), block)
+ }
+}
+
+/**
+ * @suppress **Deprecated**: for binary compatibility only
+ */
+@Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+public suspend fun <T> withTimeoutOrNull(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend () -> T): T? =
+ withTimeoutOrNull(time, unit) { block() }
+
+private class TimeoutOrNullCoroutine<T>(
+ time: Long,
+ unit: TimeUnit,
+ cont: Continuation<T?>
+) : TimeoutCoroutine<T?, T>(time, unit, cont) {
+ @Suppress("UNCHECKED_CAST")
+ override fun afterCompletion(state: Any?, mode: Int) {
+ if (state is CompletedExceptionally) {
+ val exception = state.exception
+ if (exception is TimeoutCancellationException && exception.coroutine === this)
+ cont.resumeMode(null, mode) else
+ cont.resumeWithExceptionMode(exception, mode)
+ } else
+ cont.resumeMode(state as T, mode)
+ }
+}
+
+/**
+ * This exception is thrown by [withTimeout] to indicate timeout.
+ */
+@Suppress("DEPRECATION")
+public class TimeoutCancellationException internal constructor(
+ message: String,
+ @JvmField internal val coroutine: Job?
+) : TimeoutException(message) {
+ /**
+ * Creates timeout exception with a given message.
+ */
+ public constructor(message: String) : this(message, null)
+}
+
+/**
+ * @suppress **Deprecated**: Renamed to TimeoutCancellationException
+ */
+@Deprecated("Renamed to TimeoutCancellationException", replaceWith = ReplaceWith("TimeoutCancellationException"))
+public open class TimeoutException(message: String) : CancellationException(message)
+
+@Suppress("FunctionName")
+private fun TimeoutCancellationException(
+ time: Long,
+ unit: TimeUnit,
+ coroutine: Job
+) : TimeoutCancellationException = TimeoutCancellationException("Timed out waiting for $time $unit", coroutine)
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ThreadPoolDispatcher.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ThreadPoolDispatcher.kt
similarity index 98%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ThreadPoolDispatcher.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ThreadPoolDispatcher.kt
index bcf941e..c088b62 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ThreadPoolDispatcher.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/ThreadPoolDispatcher.kt
@@ -18,7 +18,6 @@
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
-import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.experimental.CoroutineContext
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/TimeSource.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/TimeSource.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/TimeSource.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/TimeSource.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt
similarity index 87%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt
index aa2ce84..80705b3 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt
@@ -16,6 +16,7 @@
package kotlinx.coroutines.experimental
+import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
@@ -29,10 +30,14 @@
*/
suspend fun yield(): Unit = suspendCoroutineOrReturn sc@ { cont ->
val context = cont.context
- val job = context[Job]
- if (job != null && !job.isActive) throw job.getCompletionException()
+ context.checkCompletion()
if (cont !is DispatchedContinuation<Unit>) return@sc Unit
if (!cont.dispatcher.isDispatchNeeded(context)) return@sc Unit
cont.dispatchYield(Unit)
COROUTINE_SUSPENDED
}
+
+internal fun CoroutineContext.checkCompletion() {
+ val job = get(Job)
+ if (job != null && !job.isActive) throw job.getCancellationException()
+}
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt
similarity index 94%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt
index af009fa..529e638 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt
@@ -22,6 +22,8 @@
import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
import kotlinx.coroutines.experimental.removeOnCancel
import kotlinx.coroutines.experimental.selects.ALREADY_SELECTED
+import kotlinx.coroutines.experimental.selects.SelectClause1
+import kotlinx.coroutines.experimental.selects.SelectClause2
import kotlinx.coroutines.experimental.selects.SelectInstance
import kotlinx.coroutines.experimental.suspendAtomicCancellableCoroutine
import kotlin.coroutines.experimental.startCoroutine
@@ -134,8 +136,7 @@
*/
protected fun conflatePreviousSendBuffered(node: LockFreeLinkedListNode) {
val prev = node.prev
- if (prev is SendBuffered<*>)
- prev.remove()
+ (prev as? SendBuffered<*>)?.remove()
}
/**
@@ -165,8 +166,7 @@
override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) {
super.finishOnSuccess(affected, next)
// remove previous SendBuffered
- if (affected is SendBuffered<*>)
- affected.remove()
+ (affected as? SendBuffered<*>)?.remove()
}
}
@@ -315,11 +315,11 @@
}
}
- private inner class TryEnqueueSendDesc<E, R>(
+ private inner class TryEnqueueSendDesc<R>(
element: E,
select: SelectInstance<R>,
- block: suspend () -> R
- ) : AddLastDesc<SendSelect<R>>(queue, SendSelect(element, select, block)) {
+ block: suspend (SendChannel<E>) -> R
+ ) : AddLastDesc<SendSelect<E, R>>(queue, SendSelect(element, this@AbstractSendChannel, select, block)) {
override fun failure(affected: LockFreeLinkedListNode, next: Any): Any? {
if (affected is ReceiveOrClosed<*>) {
return affected as? Closed<*> ?: ENQUEUE_FAILED
@@ -339,7 +339,14 @@
}
}
- override fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend () -> R) {
+ final override val onSend: SelectClause2<E, SendChannel<E>>
+ get() = object : SelectClause2<E, SendChannel<E>> {
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, param: E, block: suspend (SendChannel<E>) -> R) {
+ registerSelectSend(select, param, block)
+ }
+ }
+
+ private fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend (SendChannel<E>) -> R) {
while (true) {
if (select.isSelected) return
if (isFull) {
@@ -357,7 +364,7 @@
offerResult === ALREADY_SELECTED -> return
offerResult === OFFER_FAILED -> {} // retry
offerResult === OFFER_SUCCESS -> {
- block.startCoroutineUndispatched(select.completion)
+ block.startCoroutineUndispatched(receiver = this, completion = select.completion)
return
}
offerResult is Closed<*> -> throw offerResult.sendException
@@ -369,17 +376,18 @@
// ------ private ------
- private class SendSelect<R>(
+ private class SendSelect<E, R>(
override val pollResult: Any?,
+ @JvmField val channel: SendChannel<E>,
@JvmField val select: SelectInstance<R>,
- @JvmField val block: suspend () -> R
+ @JvmField val block: suspend (SendChannel<E>) -> R
) : LockFreeLinkedListNode(), Send, DisposableHandle {
override fun tryResumeSend(idempotent: Any?): Any? =
if (select.trySelect(idempotent)) SELECT_STARTED else null
override fun completeResumeSend(token: Any) {
check(token === SELECT_STARTED)
- block.startCoroutine(select.completion)
+ block.startCoroutine(receiver = channel, completion = select.completion)
}
fun disposeOnSelect() {
@@ -390,7 +398,7 @@
remove()
}
- override fun toString(): String = "SendSelect($pollResult)[$select]"
+ override fun toString(): String = "SendSelect($pollResult)[$channel, $select]"
}
private class SendBuffered<out E>(
@@ -614,8 +622,15 @@
}
}
+ final override val onReceive: SelectClause1<E>
+ get() = object : SelectClause1<E> {
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E) -> R) {
+ registerSelectReceive(select, block)
+ }
+ }
+
@Suppress("UNCHECKED_CAST")
- override fun <R> registerSelectReceive(select: SelectInstance<R>, block: suspend (E) -> R) {
+ private fun <R> registerSelectReceive(select: SelectInstance<R>, block: suspend (E) -> R) {
while (true) {
if (select.isSelected) return
if (isEmpty) {
@@ -641,8 +656,15 @@
}
}
+ final override val onReceiveOrNull: SelectClause1<E?>
+ get() = object : SelectClause1<E?> {
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E?) -> R) {
+ registerSelectReceiveOrNull(select, block)
+ }
+ }
+
@Suppress("UNCHECKED_CAST")
- override fun <R> registerSelectReceiveOrNull(select: SelectInstance<R>, block: suspend (E?) -> R) {
+ private fun <R> registerSelectReceiveOrNull(select: SelectInstance<R>, block: suspend (E?) -> R) {
while (true) {
if (select.isSelected) return
if (isEmpty) {
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt
similarity index 81%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt
index 9d8c89d..5fc7390 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt
@@ -17,7 +17,9 @@
package kotlinx.coroutines.experimental.channels
import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.selects.SelectClause2
import kotlinx.coroutines.experimental.selects.SelectInstance
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
/**
@@ -57,10 +59,11 @@
* when the coroutine completes.
* The running coroutine is cancelled when the its job is [cancelled][Job.cancel].
*
- * The [context] for the new coroutine must be explicitly specified.
- * See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
*
* By default, the coroutine is immediately scheduled for execution.
* Other options can be specified via `start` parameter. See [CoroutineStart] for details.
@@ -74,13 +77,13 @@
*
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
*
- * @param context context of the coroutine
- * @param capacity capacity of the channel's buffer (no buffer by default)
- * @param start coroutine start option
- * @param block the coroutine code
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param capacity capacity of the channel's buffer (no buffer by default).
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the coroutine code.
*/
public fun <E> actor(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
capacity: Int = 0,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend ActorScope<E>.() -> Unit
@@ -105,7 +108,7 @@
parentContext: CoroutineContext,
channel: Channel<E>,
private val block: suspend ActorScope<E>.() -> Unit
-) : ActorCoroutine<E>(parentContext, channel, active = false) {
+) : ActorCoroutine<E>(parentContext, channel, active = false), SelectClause2<E, SendChannel<E>> {
override val channel: Channel<E> get() = this
override fun onStart() {
@@ -122,9 +125,13 @@
return super.offer(element)
}
- override fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend () -> R) {
+ override val onSend: SelectClause2<E, SendChannel<E>>
+ get() = this
+
+ // registerSelectSend
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, param: E, block: suspend (SendChannel<E>) -> R) {
start()
- return super.registerSelectSend(select, element, block)
+ super.onSend.registerSelectClause2(select, param, block)
}
}
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt
similarity index 84%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt
index 2bddca3..e21acd6 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt
@@ -21,8 +21,8 @@
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.Channel.Factory.CONFLATED
import kotlinx.coroutines.experimental.channels.Channel.Factory.UNLIMITED
-import kotlinx.coroutines.experimental.selects.SelectBuilder
-import kotlinx.coroutines.experimental.selects.SelectInstance
+import kotlinx.coroutines.experimental.selects.SelectClause1
+import kotlinx.coroutines.experimental.selects.SelectClause2
import kotlinx.coroutines.experimental.selects.select
import kotlinx.coroutines.experimental.yield
@@ -32,9 +32,7 @@
public interface SendChannel<in E> {
/**
* Returns `true` if this channel was closed by invocation of [close] and thus
- * the [send] attempt throws [ClosedSendChannelException]. If the channel was closed because of the exception, it
- * is considered closed, too, but it is called a _failed_ channel. All suspending attempts to send
- * an element to a failed channel throw the original [close] cause exception.
+ * the [send] and [offer] attempts throws exception.
*/
public val isClosedForSend: Boolean
@@ -46,8 +44,7 @@
/**
* Adds [element] into to this channel, suspending the caller while this channel [isFull],
- * or throws [ClosedSendChannelException] if the channel [isClosedForSend] _normally_.
- * It throws the original [close] cause exception if the channel has _failed_.
+ * or throws exception if the channel [isClosedForSend] (see [close] for details).
*
* Note, that closing a channel _after_ this function had suspended does not cause this suspended send invocation
* to abort, because closing a channel is conceptually like sending a special "close token" over this channel.
@@ -66,24 +63,26 @@
* Note, that this function does not check for cancellation when it is not suspended.
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
- * This function can be used in [select] invocation with [onSend][SelectBuilder.onSend] clause.
+ * This function can be used in [select] invocation with [onSend] clause.
* Use [offer] to try sending to this channel without waiting.
*/
public suspend fun send(element: E)
/**
- * Adds [element] into this queue if it is possible to do so immediately without violating capacity restrictions
- * and returns `true`. Otherwise, it returns `false` immediately
- * or throws [ClosedSendChannelException] if the channel [isClosedForSend] _normally_.
- * It throws the original [close] cause exception if the channel has _failed_.
+ * Clause for [select] expression of [send] suspending function that selects when the element that is specified
+ * as parameter is sent to the channel. When the clause is selected the reference to this channel
+ * is passed into the corresponding block.
+ *
+ * The [select] invocation fails with exception if the channel [isClosedForSend] (see [close] for details).
*/
- public fun offer(element: E): Boolean
+ public val onSend: SelectClause2<E, SendChannel<E>>
/**
- * Registers [onSend][SelectBuilder.onSend] select clause.
- * @suppress **This is unstable API and it is subject to change.**
+ * Adds [element] into this queue if it is possible to do so immediately without violating capacity restrictions
+ * and returns `true`. Otherwise, it returns `false` immediately
+ * or throws exception if the channel [isClosedForSend] (see [close] for details).
*/
- public fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend () -> R)
+ public fun offer(element: E): Boolean
/**
* Closes this channel with an optional exceptional [cause].
@@ -93,9 +92,9 @@
* on the side of [ReceiveChannel] starts returning `true` only after all previously sent elements
* are received.
*
- * A channel that was closed without a [cause], is considered to be _closed normally_.
- * A channel that was closed with non-null [cause] is called a _failed channel_. Attempts to send or
- * receive on a failed channel throw this cause exception.
+ * A channel that was closed without a [cause] throws [ClosedSendChannelException] on attempts to send or receive.
+ * A channel that was closed with non-null [cause] is called a _failed_ channel. Attempts to send or
+ * receive on a failed channel throw the specified [cause] exception.
*/
public fun close(cause: Throwable? = null): Boolean
}
@@ -137,14 +136,22 @@
* Note, that this function does not check for cancellation when it is not suspended.
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
- * This function can be used in [select] invocation with [onReceive][SelectBuilder.onReceive] clause.
+ * This function can be used in [select] invocation with [onReceive] clause.
* Use [poll] to try receiving from this channel without waiting.
*/
public suspend fun receive(): E
/**
+ * Clause for [select] expression of [receive] suspending function that selects with the element that
+ * is received from the channel.
+ * The [select] invocation fails with exception if the channel
+ * [isClosedForReceive] (see [close][SendChannel.close] for details).
+ */
+ public val onReceive: SelectClause1<E>
+
+ /**
* Retrieves and removes the element from this channel suspending the caller while this channel [isEmpty]
- * or returns `null` if the channel is [closed][isClosedForReceive] _normally_,
+ * or returns `null` if the channel is [closed][isClosedForReceive] without cause
* or throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
@@ -159,36 +166,32 @@
* Note, that this function does not check for cancellation when it is not suspended.
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
- * This function can be used in [select] invocation with [onReceiveOrNull][SelectBuilder.onReceiveOrNull] clause.
+ * This function can be used in [select] invocation with [onReceiveOrNull] clause.
* Use [poll] to try receiving from this channel without waiting.
*/
public suspend fun receiveOrNull(): E?
/**
- * Retrieves and removes the head of this queue, or returns `null` if this queue [isEmpty]
- * or is [closed][isClosedForReceive] _normally_,
- * or throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ * Clause for [select] expression of [receiveOrNull] suspending function that selects with the element that
+ * is received from the channel or selects with `null` if if the channel
+ * [isClosedForReceive] without cause. The [select] invocation fails with
+ * the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ */
+ public val onReceiveOrNull: SelectClause1<E?>
+
+ /**
+ * Retrieves and removes the element from this channel, or returns `null` if this channel [isEmpty]
+ * or is [isClosedForReceive] without cause.
+ * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*/
public fun poll(): E?
/**
* Returns new iterator to receive elements from this channels using `for` loop.
- * Iteration completes normally when the channel is [closed][isClosedForReceive] _normally_ and
+ * Iteration completes normally when the channel is [isClosedForReceive] without cause and
* throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*/
public operator fun iterator(): ChannelIterator<E>
-
- /**
- * Registers [onReceive][SelectBuilder.onReceive] select clause.
- * @suppress **This is unstable API and it is subject to change.**
- */
- public fun <R> registerSelectReceive(select: SelectInstance<R>, block: suspend (E) -> R)
-
- /**
- * Registers [onReceiveOrNull][SelectBuilder.onReceiveOrNull] select clause.
- * @suppress **This is unstable API and it is subject to change.**
- */
- public fun <R> registerSelectReceiveOrNull(select: SelectInstance<R>, block: suspend (E?) -> R)
}
/**
@@ -199,7 +202,7 @@
/**
* Returns `true` if the channel has more elements suspending the caller while this channel
* [isEmpty][ReceiveChannel.isEmpty] or returns `false` if the channel
- * [isClosedForReceive][ReceiveChannel.isClosedForReceive] _normally_.
+ * [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause.
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*
* This function retrieves and removes the element from this channel for the subsequent invocation
@@ -222,7 +225,7 @@
/**
* Retrieves and removes the element from this channel suspending the caller while this channel
* [isEmpty][ReceiveChannel.isEmpty] or throws [ClosedReceiveChannelException] if the channel
- * [isClosedForReceive][ReceiveChannel.isClosedForReceive].
+ * [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause.
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
@@ -298,14 +301,14 @@
/**
* Indicates attempt to [send][SendChannel.send] on [isClosedForSend][SendChannel.isClosedForSend] channel
- * that was closed _normally_. A _failed_ channel rethrows the original [close][SendChannel.close] cause
+ * that was closed without a cause. A _failed_ channel rethrows the original [close][SendChannel.close] cause
* exception on send attempts.
*/
public class ClosedSendChannelException(message: String?) : CancellationException(message)
/**
* Indicates attempt to [receive][ReceiveChannel.receive] on [isClosedForReceive][ReceiveChannel.isClosedForReceive]
- * channel that was closed _normally_. A _failed_ channel rethrows the original [close][SendChannel.close] cause
+ * channel that was closed without a cause. A _failed_ channel rethrows the original [close][SendChannel.close] cause
* exception on receive attempts.
*/
public class ClosedReceiveChannelException(message: String?) : NoSuchElementException(message)
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt
similarity index 91%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt
index 1132bec..9d092c8 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt
@@ -20,6 +20,7 @@
import kotlinx.atomicfu.loop
import kotlinx.coroutines.experimental.internal.Symbol
import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
+import kotlinx.coroutines.experimental.selects.SelectClause2
import kotlinx.coroutines.experimental.selects.SelectInstance
/**
@@ -76,8 +77,8 @@
* The most recently sent element to this channel.
*
* Access to this property throws [IllegalStateException] when this class is constructed without
- * initial value and no value was sent yet or if it was [closed][close] _normally_ and
- * throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ * initial value and no value was sent yet or if it was [closed][close] without a cause.
+ * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*/
@Suppress("UNCHECKED_CAST")
public val value: E get() {
@@ -186,9 +187,7 @@
/**
* Sends the value to all subscribed receives and stores this value as the most recent state for
* future subscribers. This implementation never suspends.
- *
- * It throws [ClosedSendChannelException] if the channel [isClosedForSend] _normally_.
- * It throws the original [close] cause exception if the channel has _failed_.
+ * It throws exception if the channel [isClosedForSend] (see [close] for details).
*/
suspend override fun send(element: E) {
offerInternal(element)?.let { throw it.sendException }
@@ -197,9 +196,7 @@
/**
* Sends the value to all subscribed receives and stores this value as the most recent state for
* future subscribers. This implementation always returns `true`.
- *
- * It throws [ClosedSendChannelException] if the channel [isClosedForSend] _normally_.
- * It throws the original [close] cause exception if the channel has _failed_.
+ * It throws exception if the channel [isClosedForSend] (see [close] for details).
*/
override fun offer(element: E): Boolean {
offerInternal(element)?.let { throw it.sendException }
@@ -233,13 +230,20 @@
}
}
- override fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend () -> R) {
+ override val onSend: SelectClause2<E, SendChannel<E>>
+ get() = object : SelectClause2<E, SendChannel<E>> {
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, param: E, block: suspend (SendChannel<E>) -> R) {
+ registerSelectSend(select, param, block)
+ }
+ }
+
+ private fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend (SendChannel<E>) -> R) {
if (!select.trySelect(null)) return
offerInternal(element)?.let {
select.resumeSelectCancellableWithException(it.sendException)
return
}
- block.startCoroutineUndispatched(select.completion)
+ block.startCoroutineUndispatched(receiver = this, completion = select.completion)
}
private class Subscriber<E>(
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt
similarity index 87%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt
index 0590ebe..0ed752d 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt
@@ -16,10 +16,8 @@
package kotlinx.coroutines.experimental.channels
-import kotlinx.coroutines.experimental.CoroutineDispatcher
-import kotlinx.coroutines.experimental.CoroutineScope
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.*
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
@@ -72,22 +70,23 @@
* when the coroutine completes.
* The running coroutine is cancelled when the its job is [cancelled][Job.cancel].
*
- * The [context] for the new coroutine must be explicitly specified.
- * See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
*
* Uncaught exceptions in this coroutine close the channel with this exception as a cause and
* the resulting channel becomes _failed_, so that any attempt to receive from such a channel throws exception.
*
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
*
- * @param context context of the coroutine
- * @param capacity capacity of the channel's buffer (no buffer by default)
- * @param block the coroutine code
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param capacity capacity of the channel's buffer (no buffer by default).
+ * @param block the coroutine code.
*/
public fun <E> produce(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
capacity: Int = 0,
block: suspend ProducerScope<E>.() -> Unit
): ProducerJob<E> {
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Symbol.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Symbol.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Symbol.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Symbol.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ThreadSafeHeap.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ThreadSafeHeap.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ThreadSafeHeap.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ThreadSafeHeap.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/intrinsics/Undispatched.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/intrinsics/Undispatched.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/intrinsics/Undispatched.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/intrinsics/Undispatched.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
similarity index 78%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
index 1bfff3c..e38da5c 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
@@ -19,8 +19,6 @@
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.loop
import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.experimental.channels.ClosedSendChannelException
import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.internal.*
@@ -37,49 +35,25 @@
*/
public interface SelectBuilder<in R> {
/**
- * Clause for [Job.join] suspending function that selects the given [block] when the job is complete.
- * This clause never fails, even if the job completes exceptionally.
+ * Registers clause in this [select] expression without additional parameters that does not select any value.
*/
- public fun Job.onJoin(block: suspend () -> R)
+ public operator fun SelectClause0.invoke(block: suspend () -> R)
/**
- * Clause for [Deferred.await] suspending function that selects the given [block] with the deferred value is
- * resolved. The [select] invocation fails if the deferred value completes exceptionally (either fails or
- * it cancelled).
+ * Registers clause in this [select] expression without additional parameters that selects value of type [Q].
*/
- public fun <T> Deferred<T>.onAwait(block: suspend (T) -> R)
+ public operator fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R)
/**
- * Clause for [SendChannel.send] suspending function that selects the given [block] when the [element] is sent to
- * the channel. The [select] invocation fails with [ClosedSendChannelException] if the channel
- * [isClosedForSend][SendChannel.isClosedForSend] _normally_ or with the original
- * [close][SendChannel.close] cause exception if the channel has _failed_.
+ * Registers clause in this [select] expression with additional parameter of type [P] that selects value of type [Q].
*/
- public fun <E> SendChannel<E>.onSend(element: E, block: suspend () -> R)
+ public operator fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R)
/**
- * Clause for [ReceiveChannel.receive] suspending function that selects the given [block] with the element that
- * is received from the channel. The [select] invocation fails with [ClosedReceiveChannelException] if the channel
- * [isClosedForReceive][ReceiveChannel.isClosedForReceive] _normally_ or with the original
- * [close][SendChannel.close] cause exception if the channel has _failed_.
+ * Registers clause in this [select] expression with additional parameter nullable parameter of type [P]
+ * with the `null` value for this parameter that selects value of type [Q].
*/
- public fun <E> ReceiveChannel<E>.onReceive(block: suspend (E) -> R)
-
- /**
- * Clause for [ReceiveChannel.receiveOrNull] suspending function that selects the given [block] with the element that
- * is received from the channel or selects the given [block] with `null` if if the channel
- * [isClosedForReceive][ReceiveChannel.isClosedForReceive] _normally_. The [select] invocation fails with
- * the original [close][SendChannel.close] cause exception if the channel has _failed_.
- */
- public fun <E> ReceiveChannel<E>.onReceiveOrNull(block: suspend (E?) -> R)
-
- /**
- * Clause for [Mutex.lock] suspending function that selects the given [block] when the mutex is locked.
- *
- * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex
- * is already locked with the same token (same identity), this clause throws [IllegalStateException].
- */
- public fun Mutex.onLock(owner: Any? = null, block: suspend () -> R)
+ public operator fun <P, Q> SelectClause2<P?, Q>.invoke(block: suspend (Q) -> R) = invoke(null, block)
/**
* Clause that selects the given [block] after a specified timeout passes.
@@ -88,6 +62,63 @@
* @param unit timeout unit (milliseconds by default)
*/
public fun onTimeout(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend () -> R)
+
+ /** @suppress **Deprecated: for binary compatibility only **/
+ @Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+ public fun Job.onJoin(block: suspend () -> R) { onJoin(block) }
+
+ /** @suppress **Deprecated: for binary compatibility only **/
+ @Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+ public fun <T> Deferred<T>.onAwait(block: suspend (T) -> R) { onAwait(block) }
+
+ /** @suppress **Deprecated: for binary compatibility only **/
+ @Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+ public fun Mutex.onLock(owner: Any? = null, block: suspend () -> R) { onLock { block() } }
+
+ /** @suppress **Deprecated: for binary compatibility only **/
+ @Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+ public fun <E> SendChannel<E>.onSend(element: E, block: suspend () -> R) { onSend(element) { block() } }
+
+ /** @suppress **Deprecated: for binary compatibility only **/
+ @Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+ public fun <E> ReceiveChannel<E>.onReceive(block: suspend (E) -> R) { onReceive(block) }
+
+ /** @suppress **Deprecated: for binary compatibility only **/
+ @Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+ public fun <E> ReceiveChannel<E>.onReceiveOrNull(block: suspend (E?) -> R) { onReceiveOrNull(block) }
+}
+
+/**
+ * Clause for [select] expression without additional parameters that does not select any value.
+ */
+public interface SelectClause0 {
+ /**
+ * Registers this clause with the specified [select] instance and [block] of code.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ public fun <R> registerSelectClause0(select: SelectInstance<R>, block: suspend () -> R)
+}
+
+/**
+ * Clause for [select] expression without additional parameters that selects value of type [Q].
+ */
+public interface SelectClause1<out Q> {
+ /**
+ * Registers this clause with the specified [select] instance and [block] of code.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ public fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (Q) -> R)
+}
+
+/**
+ * Clause for [select] expression with additional parameter of type [P] that selects value of type [Q].
+ */
+public interface SelectClause2<in P, out Q> {
+ /**
+ * Registers this clause with the specified [select] instance and [block] of code.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ public fun <R> registerSelectClause2(select: SelectInstance<R>, param: P, block: suspend (Q) -> R)
}
/**
@@ -120,15 +151,18 @@
/**
* Returns completion continuation of this select instance.
* This select instance must be _selected_ first.
- * All resumption through this instance happen _directly_ (as if `mode` is [MODE_DIRECT]).
+ * All resumption through this instance happen _directly_ without going through dispatcher ([MODE_DIRECT]).
*/
public val completion: Continuation<R>
/**
- * Resumes this instance with [MODE_CANCELLABLE].
+ * Resumes this instance in a cancellable way ([MODE_CANCELLABLE]).
*/
public fun resumeSelectCancellableWithException(exception: Throwable)
+ /**
+ * Disposes the specified handle when this instance is selected.
+ */
public fun disposeOnSelect(handle: DisposableHandle)
}
@@ -275,16 +309,17 @@
private fun initCancellability() {
val parent = context[Job] ?: return
- val newRegistration = parent.invokeOnCompletion(SelectOnCancellation(parent), onCancelling = true)
+ val newRegistration = parent.invokeOnCompletion(onCancelling = true, handler = SelectOnCancellation(parent))
parentHandle = newRegistration
// now check our state _after_ registering
if (isSelected) newRegistration.dispose()
}
private inner class SelectOnCancellation(job: Job) : JobCancellationNode<Job>(job) {
- override fun invokeOnce(reason: Throwable?) {
+ // Note: may be invoked multiple times, but only the first trySelect succeeds anyway
+ override fun invoke(reason: Throwable?) {
if (trySelect(null))
- resumeSelectCancellableWithException(reason ?: CancellationException("Select was cancelled"))
+ resumeSelectCancellableWithException(job.getCancellationException())
}
override fun toString(): String = "SelectOnCancellation[${this@SelectBuilderImpl}]"
}
@@ -393,28 +428,16 @@
}
}
- override fun Job.onJoin(block: suspend () -> R) {
- registerSelectJoin(this@SelectBuilderImpl, block)
+ override fun SelectClause0.invoke(block: suspend () -> R) {
+ registerSelectClause0(this@SelectBuilderImpl, block)
}
- override fun <T> Deferred<T>.onAwait(block: suspend (T) -> R) {
- registerSelectAwait(this@SelectBuilderImpl, block)
+ override fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) {
+ registerSelectClause1(this@SelectBuilderImpl, block)
}
- override fun <E> SendChannel<E>.onSend(element: E, block: suspend () -> R) {
- registerSelectSend(this@SelectBuilderImpl, element, block)
- }
-
- override fun <E> ReceiveChannel<E>.onReceive(block: suspend (E) -> R) {
- registerSelectReceive(this@SelectBuilderImpl, block)
- }
-
- override fun <E> ReceiveChannel<E>.onReceiveOrNull(block: suspend (E?) -> R) {
- registerSelectReceiveOrNull(this@SelectBuilderImpl, block)
- }
-
- override fun Mutex.onLock(owner: Any?, block: suspend () -> R) {
- registerSelectLock(this@SelectBuilderImpl, owner, block)
+ override fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) {
+ registerSelectClause2(this@SelectBuilderImpl, param, block)
}
override fun onTimeout(time: Long, unit: TimeUnit, block: suspend () -> R) {
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt
similarity index 69%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt
index b6b84bc..cf79b47 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt
@@ -16,11 +16,6 @@
package kotlinx.coroutines.experimental.selects
-import kotlinx.coroutines.experimental.Deferred
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.channels.ReceiveChannel
-import kotlinx.coroutines.experimental.channels.SendChannel
-import kotlinx.coroutines.experimental.sync.Mutex
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.coroutines.experimental.Continuation
@@ -69,28 +64,16 @@
return instance.getResult()
}
- override fun Job.onJoin(block: suspend () -> R) {
- clauses += { registerSelectJoin(instance, block) }
+ override fun SelectClause0.invoke(block: suspend () -> R) {
+ clauses += { registerSelectClause0(instance, block) }
}
- override fun <T> Deferred<T>.onAwait(block: suspend (T) -> R) {
- clauses += { registerSelectAwait(instance, block) }
+ override fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) {
+ clauses += { registerSelectClause1(instance, block) }
}
- override fun <E> SendChannel<E>.onSend(element: E, block: suspend () -> R) {
- clauses += { registerSelectSend(instance, element, block) }
- }
-
- override fun <E> ReceiveChannel<E>.onReceive(block: suspend (E) -> R) {
- clauses += { registerSelectReceive(instance, block) }
- }
-
- override fun <E> ReceiveChannel<E>.onReceiveOrNull(block: suspend (E?) -> R) {
- clauses += { registerSelectReceiveOrNull(instance, block) }
- }
-
- override fun Mutex.onLock(owner: Any?, block: suspend () -> R) {
- clauses += { registerSelectLock(instance, owner, block) }
+ override fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) {
+ clauses += { registerSelectClause2(instance, param, block) }
}
override fun onTimeout(time: Long, unit: TimeUnit, block: suspend () -> R) {
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt
similarity index 94%
rename from kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt
rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt
index 9028ba3..3130085 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt
@@ -22,7 +22,7 @@
import kotlinx.coroutines.experimental.internal.*
import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
import kotlinx.coroutines.experimental.selects.ALREADY_SELECTED
-import kotlinx.coroutines.experimental.selects.SelectBuilder
+import kotlinx.coroutines.experimental.selects.SelectClause2
import kotlinx.coroutines.experimental.selects.SelectInstance
import kotlinx.coroutines.experimental.selects.select
import kotlin.coroutines.experimental.startCoroutine
@@ -76,7 +76,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][SelectBuilder.onLock] clause.
+ * This function can be used in [select] invocation with [onLock] clause.
* Use [tryLock] to try acquire lock without waiting.
*
* @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex
@@ -85,10 +85,11 @@
public suspend fun lock(owner: Any? = null)
/**
- * Registers [onLock][SelectBuilder.onLock] select clause.
- * @suppress **This is unstable API and it is subject to change.**
+ * Clause for [select] expression of [lock] suspending function that selects when the mutex is locked.
+ * Additional parameter for the clause in the `owner` (see [lock]) and when the clause is selected
+ * the reference to this mutex is passed into the corresponding block.
*/
- public fun <R> registerSelectLock(select: SelectInstance<R>, owner: Any?, block: suspend () -> R)
+ public val onLock: SelectClause2<Any?, Mutex>
/**
* Checks mutex locked by owner
@@ -169,7 +170,7 @@
override fun toString(): String = "Empty[$locked]"
}
-internal class MutexImpl(locked: Boolean) : Mutex {
+internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
// State is: Empty | LockedQueue | OpDescriptor
// shared objects while we have no waiters
private val _state = atomic<Any?>(if (locked) EmptyLocked else EmptyUnlocked)
@@ -251,7 +252,12 @@
}
}
- override fun <R> registerSelectLock(select: SelectInstance<R>, owner: Any?, block: suspend () -> R) {
+ override val onLock: SelectClause2<Any?, Mutex>
+ get() = this
+
+ // registerSelectLock
+ @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, owner: Any?, block: suspend (Mutex) -> R) {
while (true) { // lock-free loop on state
if (select.isSelected) return
val state = _state.value
@@ -264,7 +270,7 @@
val failure = select.performAtomicTrySelect(TryLockDesc(this, owner))
when {
failure == null -> { // success
- block.startCoroutineUndispatched(select.completion)
+ block.startCoroutineUndispatched(receiver = this, completion = select.completion)
return
}
failure === ALREADY_SELECTED -> return // already selected -- bail out
@@ -325,8 +331,8 @@
owner: Any?,
queue: LockedQueue,
select: SelectInstance<R>,
- block: suspend () -> R
- ) : AddLastDesc<LockSelect<R>>(queue, LockSelect(owner, select, block)) {
+ block: suspend (Mutex) -> R
+ ) : AddLastDesc<LockSelect<R>>(queue, LockSelect(owner, mutex, select, block)) {
override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? {
if (mutex._state.value !== queue) return ENQUEUE_FAIL
return super.onPrepare(affected, next)
@@ -455,15 +461,16 @@
private class LockSelect<R>(
owner: Any?,
+ @JvmField val mutex: Mutex,
@JvmField val select: SelectInstance<R>,
- @JvmField val block: suspend () -> R
+ @JvmField val block: suspend (Mutex) -> R
) : LockWaiter(owner) {
override fun tryResumeLockWaiter(): Any? = if (select.trySelect(null)) SELECT_SUCCESS else null
override fun completeResumeLockWaiter(token: Any) {
check(token === SELECT_SUCCESS)
- block.startCoroutine(select.completion)
+ block.startCoroutine(receiver = mutex, completion = select.completion)
}
- override fun toString(): String = "LockSelect[$owner, $select]"
+ override fun toString(): String = "LockSelect[$owner, $mutex, $select]"
}
// atomic unlock operation that checks that waiters queue is empty
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-01.kt
similarity index 88%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-01.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-01.kt
index b920fe8..a195875 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-01.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-01.kt
@@ -17,10 +17,11 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.basic.example01
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
fun main(args: Array<String>) {
- launch(CommonPool) { // create new coroutine in common thread pool
+ launch { // launch new coroutine
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-02.kt
similarity index 84%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-02.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-02.kt
index 6e3a9ee..27b4864 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-02.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-02.kt
@@ -17,10 +17,12 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.basic.example02
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
- launch(CommonPool) { // create new coroutine in common thread pool
+ launch { // launch new coroutine
delay(1000L)
println("World!")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-03.kt
similarity index 80%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-03.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-03.kt
index cc1b748..45002ff 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-03.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-03.kt
@@ -17,10 +17,12 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.basic.example03
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) { // create new coroutine and keep a reference to its Job
+ val job = launch { // launch new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-04.kt
similarity index 83%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-04.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-04.kt
index 231305e..3978226 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-04.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-04.kt
@@ -17,10 +17,12 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.basic.example04
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) { doWorld() }
+ val job = launch { doWorld() }
println("Hello,")
job.join()
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-05.kt
similarity index 80%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-05.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-05.kt
index ec76812..07e657f 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-05.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-05.kt
@@ -17,11 +17,13 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.basic.example05
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
- val jobs = List(100_000) { // create a lot of coroutines and list their jobs
- launch(CommonPool) {
+ val jobs = List(100_000) { // launch a lot of coroutines and list their jobs
+ launch {
delay(1000L)
print(".")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt
similarity index 85%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt
index 0cf0422..5a967e6 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt
@@ -17,10 +17,12 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.basic.example06
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
- launch(CommonPool) {
+ launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-01.kt
similarity index 83%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-01.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-01.kt
index 4dd92a6..fd92d4b 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-01.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-01.kt
@@ -17,10 +17,12 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.cancel.example01
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) {
+ val job = launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
@@ -29,6 +31,6 @@
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
- delay(1300L) // delay a bit to ensure it was cancelled indeed
+ job.join() // waits for job's completion
println("main: Now I can quit.")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-02.kt
similarity index 77%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-02.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-02.kt
index 1e99b34..35180ff 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-02.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-02.kt
@@ -17,14 +17,17 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.cancel.example02
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.cancelAndJoin
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
- val job = launch(CommonPool) {
+ val job = launch {
var nextPrintTime = startTime
var i = 0
- while (i < 10) { // computation loop, just wastes CPU
+ while (i < 5) { // computation loop, just wastes CPU
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("I'm sleeping ${i++} ...")
@@ -34,7 +37,6 @@
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- delay(1300L) // delay a bit to see if it was cancelled....
+ job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-03.kt
similarity index 81%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-03.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-03.kt
index 8746d38..3264726 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-03.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-03.kt
@@ -17,11 +17,14 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.cancel.example03
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.cancelAndJoin
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
- val job = launch(CommonPool) {
+ val job = launch {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
@@ -34,7 +37,6 @@
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- delay(1300L) // delay a bit to see if it was cancelled....
+ job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-04.kt
similarity index 79%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-04.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-04.kt
index 28df084..7aa05c6 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-04.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-04.kt
@@ -17,10 +17,13 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.cancel.example04
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.cancelAndJoin
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) {
+ val job = launch {
try {
repeat(1000) { i ->
println("I'm sleeping $i ...")
@@ -32,7 +35,6 @@
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- delay(1300L) // delay a bit to ensure it was cancelled indeed
+ job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-05.kt
similarity index 90%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-05.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-05.kt
index d379c08..fda1cc6 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-05.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-05.kt
@@ -20,7 +20,7 @@
import kotlinx.coroutines.experimental.*
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) {
+ val job = launch {
try {
repeat(1000) { i ->
println("I'm sleeping $i ...")
@@ -36,7 +36,6 @@
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- delay(1300L) // delay a bit to ensure it was cancelled indeed
+ job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-06.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-06.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-06.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-06.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-07.kt
similarity index 83%
copy from kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt
copy to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-07.kt
index 0cf0422..1a197a4 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-07.kt
@@ -15,16 +15,17 @@
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
-package guide.basic.example06
+package guide.cancel.example07
import kotlinx.coroutines.experimental.*
fun main(args: Array<String>) = runBlocking<Unit> {
- launch(CommonPool) {
+ val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
+ "Done" // will get cancelled before it produces this result
}
- delay(1300L) // just quit after delay
+ println("Result is $result")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-01.kt
similarity index 86%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-01.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-01.kt
index 4f560dc..e853fd3 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-01.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-01.kt
@@ -17,12 +17,13 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.channel.example01
-import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.*
+import kotlinx.coroutines.experimental.channels.Channel
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
val channel = Channel<Int>()
- launch(CommonPool) {
+ launch {
// this might be heavy CPU-consuming computation or async logic, we'll just send five squares
for (x in 1..5) channel.send(x * x)
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-02.kt
similarity index 86%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-02.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-02.kt
index 3d41608..0b93841 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-02.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-02.kt
@@ -17,12 +17,13 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.channel.example02
-import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.*
+import kotlinx.coroutines.experimental.channels.Channel
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
val channel = Channel<Int>()
- launch(CommonPool) {
+ launch {
for (x in 1..5) channel.send(x * x)
channel.close() // we're done sending
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-03.kt
similarity index 81%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-03.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-03.kt
index e9132b1..3270fc1 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-03.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-03.kt
@@ -17,10 +17,11 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.channel.example03
-import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.*
+import kotlinx.coroutines.experimental.channels.consumeEach
+import kotlinx.coroutines.experimental.channels.produce
+import kotlinx.coroutines.experimental.runBlocking
-fun produceSquares() = produce<Int>(CommonPool) {
+fun produceSquares() = produce<Int> {
for (x in 1..5) send(x * x)
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-04.kt
similarity index 82%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-04.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-04.kt
index 4d095c4..736bbc2 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-04.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-04.kt
@@ -17,15 +17,16 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.channel.example04
-import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.*
+import kotlinx.coroutines.experimental.channels.ReceiveChannel
+import kotlinx.coroutines.experimental.channels.produce
+import kotlinx.coroutines.experimental.runBlocking
-fun produceNumbers() = produce<Int>(CommonPool) {
+fun produceNumbers() = produce<Int> {
var x = 1
while (true) send(x++) // infinite stream of integers starting from 1
}
-fun square(numbers: ReceiveChannel<Int>) = produce<Int>(CommonPool) {
+fun square(numbers: ReceiveChannel<Int>) = produce<Int> {
for (x in numbers) send(x * x)
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt
similarity index 94%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt
index 95a1cac..b14666e 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt
@@ -37,4 +37,5 @@
println(prime)
cur = filter(coroutineContext, cur, prime)
}
+ coroutineContext.cancelChildren() // cancel all children to let main finish
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-06.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-06.kt
similarity index 77%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-06.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-06.kt
index c4d5a28..ddead7a 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-06.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-06.kt
@@ -17,10 +17,14 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.channel.example06
-import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.*
+import kotlinx.coroutines.experimental.channels.ReceiveChannel
+import kotlinx.coroutines.experimental.channels.consumeEach
+import kotlinx.coroutines.experimental.channels.produce
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
-fun produceNumbers() = produce<Int>(CommonPool) {
+fun produceNumbers() = produce<Int> {
var x = 1 // start from 1
while (true) {
send(x++) // produce next
@@ -28,7 +32,7 @@
}
}
-fun launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch(CommonPool) {
+fun launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
channel.consumeEach {
println("Processor #$id received $it")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt
similarity index 93%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt
index 11cbc22..d10c5ed 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt
@@ -34,4 +34,5 @@
repeat(6) { // receive first six
println(channel.receive())
}
+ coroutineContext.cancelChildren() // cancel all children to let main finish
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt
similarity index 90%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt
index 81d97f7..096f435 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt
@@ -22,7 +22,7 @@
fun main(args: Array<String>) = runBlocking<Unit> {
val channel = Channel<Int>(4) // create buffered channel
- launch(coroutineContext) { // launch sender coroutine
+ val sender = launch(coroutineContext) { // launch sender coroutine
repeat(10) {
println("Sending $it") // print before sending each element
channel.send(it) // will suspend when buffer is full
@@ -30,4 +30,5 @@
}
// don't receive anything... just wait....
delay(1000)
+ sender.cancel() // cancel sender coroutine
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt
similarity index 95%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt
index c9e744f..2a5e064 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt
@@ -28,7 +28,7 @@
launch(coroutineContext) { player("pong", table) }
table.send(Ball(0)) // serve the ball
delay(1000) // delay 1 second
- table.receive() // game over, grab the ball
+ coroutineContext.cancelChildren() // game over, cancel them
}
suspend fun player(name: String, table: Channel<Ball>) {
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt
similarity index 83%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt
index 63965e9..1ff7b38 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt
@@ -17,7 +17,9 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.compose.example02
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.async
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.runBlocking
import kotlin.system.measureTimeMillis
suspend fun doSomethingUsefulOne(): Int {
@@ -32,8 +34,8 @@
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
- val one = async(CommonPool) { doSomethingUsefulOne() }
- val two = async(CommonPool) { doSomethingUsefulTwo() }
+ val one = async { doSomethingUsefulOne() }
+ val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt
similarity index 77%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt
index cd56a18..80db0fe 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt
@@ -17,7 +17,10 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.compose.example03
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.CoroutineStart
+import kotlinx.coroutines.experimental.async
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.runBlocking
import kotlin.system.measureTimeMillis
suspend fun doSomethingUsefulOne(): Int {
@@ -32,8 +35,8 @@
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
- val one = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulOne() }
- val two = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulTwo() }
+ val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
+ val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt
similarity index 89%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt
index aaedcb7..6f6bbcf 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt
@@ -17,7 +17,9 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.compose.example04
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.async
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.runBlocking
import kotlin.system.measureTimeMillis
suspend fun doSomethingUsefulOne(): Int {
@@ -31,12 +33,12 @@
}
// The result type of asyncSomethingUsefulOne is Deferred<Int>
-fun asyncSomethingUsefulOne() = async(CommonPool) {
+fun asyncSomethingUsefulOne() = async {
doSomethingUsefulOne()
}
// The result type of asyncSomethingUsefulTwo is Deferred<Int>
-fun asyncSomethingUsefulTwo() = async(CommonPool) {
+fun asyncSomethingUsefulTwo() = async {
doSomethingUsefulTwo()
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-04.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-04.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-04.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt
similarity index 85%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt
index 546c3bc..b601256 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt
@@ -17,13 +17,15 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.context.example06
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking<Unit> {
- // start a coroutine to process some kind of incoming request
- val request = launch(CommonPool) {
+ // launch a coroutine to process some kind of incoming request
+ val request = launch {
// it spawns two other jobs, one with its separate context
- val job1 = launch(CommonPool) {
+ val job1 = launch {
println("job1: I have my own context and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt
diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-08.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-08.kt
new file mode 100644
index 0000000..b924788
--- /dev/null
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-08.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
+package guide.context.example08
+
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
+
+fun main(args: Array<String>) = runBlocking<Unit> {
+ // launch a coroutine to process some kind of incoming request
+ val request = launch {
+ repeat(3) { i -> // launch a few children jobs
+ launch(coroutineContext) {
+ delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
+ println("Coroutine $i is done")
+ }
+ }
+ println("request: I'm done and I don't explicitly join my children that are still active")
+ }
+ request.join() // wait for completion of the request, including all its children
+ println("Now processing of the request is complete")
+}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-08.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-09.kt
similarity index 76%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-08.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-09.kt
index 7cbfb21..b6a584e 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-08.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-09.kt
@@ -15,21 +15,24 @@
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
-package guide.context.example08
+package guide.context.example09
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.CoroutineName
+import kotlinx.coroutines.experimental.async
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.runBlocking
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main(args: Array<String>) = runBlocking(CoroutineName("main")) {
log("Started main coroutine")
// run two background value computations
- val v1 = async(CommonPool + CoroutineName("v1coroutine")) {
+ val v1 = async(CoroutineName("v1coroutine")) {
log("Computing v1")
delay(500)
252
}
- val v2 = async(CommonPool + CoroutineName("v2coroutine")) {
+ val v2 = async(CoroutineName("v2coroutine")) {
log("Computing v2")
delay(1000)
6
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-09.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt
similarity index 83%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-context-09.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt
index 44ba5dc..2222cf8 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-09.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt
@@ -15,7 +15,7 @@
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
-package guide.context.example09
+package guide.context.example10
import kotlinx.coroutines.experimental.*
@@ -25,13 +25,12 @@
val coroutines = List(10) { i ->
// they are all children of our job object
launch(coroutineContext + job) { // we use the context of main runBlocking thread, but with our own job object
- delay(i * 200L) // variable delay 0ms, 200ms, 400ms, ... etc
+ delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
println("Coroutine $i is done")
}
}
println("Launched ${coroutines.size} coroutines")
delay(500L) // delay for half a second
- println("Cancelling job!")
- job.cancel() // cancel our job.. !!!
- delay(1000L) // delay for more to see if our coroutines are still working
+ println("Cancelling the job!")
+ job.cancelAndJoin() // cancel all our coroutines and wait for all of them to complete
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt
similarity index 95%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt
index 3c952d6..9e46a7c 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt
@@ -53,4 +53,5 @@
repeat(7) {
selectFizzBuzz(fizz, buzz)
}
+ coroutineContext.cancelChildren() // cancel fizz & buzz coroutines
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt
similarity index 97%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt
index b3ff0e8..b128877 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt
@@ -48,4 +48,5 @@
repeat(8) { // print first eight results
println(selectAorB(a, b))
}
+ coroutineContext.cancelChildren()
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt
similarity index 85%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt
index e754904..acff08f 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt
@@ -20,8 +20,9 @@
import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.*
import kotlinx.coroutines.experimental.selects.*
+import kotlin.coroutines.experimental.CoroutineContext
-fun produceNumbers(side: SendChannel<Int>) = produce<Int>(CommonPool) {
+fun produceNumbers(context: CoroutineContext, side: SendChannel<Int>) = produce<Int>(context) {
for (num in 1..10) { // produce 10 numbers from 1 to 10
delay(100) // every 100 ms
select<Unit> {
@@ -36,9 +37,10 @@
launch(coroutineContext) { // this is a very fast consumer for the side channel
side.consumeEach { println("Side channel has $it") }
}
- produceNumbers(side).consumeEach {
+ produceNumbers(coroutineContext, side).consumeEach {
println("Consuming $it")
delay(250) // let us digest the consumed number properly, do not hurry
}
println("Done consuming")
+ coroutineContext.cancelChildren()
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt
similarity index 82%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt
index 1748834..07d959d 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt
@@ -17,12 +17,14 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.select.example04
-import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.*
-import kotlinx.coroutines.experimental.selects.*
+import kotlinx.coroutines.experimental.Deferred
+import kotlinx.coroutines.experimental.async
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.runBlocking
+import kotlinx.coroutines.experimental.selects.select
import java.util.*
-fun asyncString(time: Int) = async(CommonPool) {
+fun asyncString(time: Int) = async {
delay(time.toLong())
"Waited for $time ms"
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt
similarity index 88%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt
index d2cdcd7..6d35cf0 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt
@@ -18,10 +18,12 @@
package guide.select.example05
import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.*
-import kotlinx.coroutines.experimental.selects.*
+import kotlinx.coroutines.experimental.channels.Channel
+import kotlinx.coroutines.experimental.channels.ReceiveChannel
+import kotlinx.coroutines.experimental.channels.produce
+import kotlinx.coroutines.experimental.selects.select
-fun switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String>(CommonPool) {
+fun switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> {
var current = input.receive() // start with first received deferred value
while (isActive) { // loop while not cancelled/closed
val next = select<Deferred<String>?> { // return next deferred value from this select or null
@@ -42,7 +44,7 @@
}
}
-fun asyncString(str: String, time: Long) = async(CommonPool) {
+fun asyncString(str: String, time: Long) = async {
delay(time)
str
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt
similarity index 92%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt
index 279b51d..73fe116 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt
@@ -21,6 +21,7 @@
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.system.measureTimeMillis
import kotlinx.coroutines.experimental.sync.Mutex
+import kotlinx.coroutines.experimental.sync.withLock
suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) {
val n = 1000 // number of coroutines to launch
@@ -41,9 +42,9 @@
fun main(args: Array<String>) = runBlocking<Unit> {
massiveRun(CommonPool) {
- mutex.lock()
- try { counter++ }
- finally { mutex.unlock() }
+ mutex.withLock {
+ counter++
+ }
}
println("Counter = $counter")
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt
similarity index 87%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt
index 035f05e..29d76fd 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt
@@ -17,10 +17,13 @@
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.sync.example07
-import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.CommonPool
+import kotlinx.coroutines.experimental.CompletableDeferred
+import kotlinx.coroutines.experimental.channels.actor
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.system.measureTimeMillis
-import kotlinx.coroutines.experimental.channels.*
suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) {
val n = 1000 // number of coroutines to launch
@@ -42,7 +45,7 @@
class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
// This function launches a new counter actor
-fun counterActor() = actor<CounterMsg>(CommonPool) {
+fun counterActor() = actor<CounterMsg> {
var counter = 0 // actor state
for (msg in channel) { // iterate over incoming messages
when (msg) {
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/test/GuideTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/test/GuideTest.kt
similarity index 94%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/test/GuideTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/test/GuideTest.kt
index c9e2a89..e4beeb6 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/guide/test/GuideTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/test/GuideTest.kt
@@ -73,7 +73,6 @@
"main: I'm tired of waiting!",
"I'm sleeping 3 ...",
"I'm sleeping 4 ...",
- "I'm sleeping 5 ...",
"main: Now I can quit."
)
}
@@ -120,7 +119,17 @@
"I'm sleeping 0 ...",
"I'm sleeping 1 ...",
"I'm sleeping 2 ...",
- "Exception in thread \"main\" kotlinx.coroutines.experimental.TimeoutException: Timed out waiting for 1300 MILLISECONDS"
+ "Exception in thread \"main\" kotlinx.coroutines.experimental.TimeoutCancellationException: Timed out waiting for 1300 MILLISECONDS"
+ )
+ }
+
+ @Test
+ fun testGuideCancelExample07() {
+ test("GuideCancelExample07") { guide.cancel.example07.main(emptyArray()) }.verifyLines(
+ "I'm sleeping 0 ...",
+ "I'm sleeping 1 ...",
+ "I'm sleeping 2 ...",
+ "Result is null"
)
}
@@ -197,7 +206,7 @@
@Test
fun testGuideContextExample05() {
test("GuideContextExample05") { guide.context.example05.main(emptyArray()) }.also { lines ->
- check(lines.size == 1 && lines[0].startsWith("My job is BlockingCoroutine{Active}@"))
+ check(lines.size == 1 && lines[0].startsWith("My job is \"coroutine#1\":BlockingCoroutine{Active}@"))
}
}
@@ -221,7 +230,18 @@
@Test
fun testGuideContextExample08() {
- test("GuideContextExample08") { guide.context.example08.main(emptyArray()) }.verifyLinesFlexibleThread(
+ test("GuideContextExample08") { guide.context.example08.main(emptyArray()) }.verifyLines(
+ "request: I'm done and I don't explicitly join my children that are still active",
+ "Coroutine 0 is done",
+ "Coroutine 1 is done",
+ "Coroutine 2 is done",
+ "Now processing of the request is complete"
+ )
+ }
+
+ @Test
+ fun testGuideContextExample09() {
+ test("GuideContextExample09") { guide.context.example09.main(emptyArray()) }.verifyLinesFlexibleThread(
"[main @main#1] Started main coroutine",
"[ForkJoinPool.commonPool-worker-1 @v1coroutine#2] Computing v1",
"[ForkJoinPool.commonPool-worker-2 @v2coroutine#3] Computing v2",
@@ -230,13 +250,12 @@
}
@Test
- fun testGuideContextExample09() {
- test("GuideContextExample09") { guide.context.example09.main(emptyArray()) }.verifyLines(
+ fun testGuideContextExample10() {
+ test("GuideContextExample10") { guide.context.example10.main(emptyArray()) }.verifyLines(
"Launched 10 coroutines",
"Coroutine 0 is done",
"Coroutine 1 is done",
- "Coroutine 2 is done",
- "Cancelling job!"
+ "Cancelling the job!"
)
}
@@ -340,8 +359,7 @@
"ping Ball(hits=1)",
"pong Ball(hits=2)",
"ping Ball(hits=3)",
- "pong Ball(hits=4)",
- "ping Ball(hits=5)"
+ "pong Ball(hits=4)"
)
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/guide/test/TestUtil.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/test/TestUtil.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/guide/test/TestUtil.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/guide/test/TestUtil.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncLazyTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncLazyTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncLazyTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncLazyTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt
similarity index 83%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt
index 27ad4e4..65354b1 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt
@@ -18,6 +18,7 @@
import org.hamcrest.core.IsEqual
import org.hamcrest.core.IsInstanceOf
+import org.hamcrest.core.IsNull
import org.junit.Assert.assertThat
import org.junit.Assert.fail
import org.junit.Test
@@ -30,8 +31,9 @@
assertThat(c.isCancelled, IsEqual(false))
assertThat(c.isCompleted, IsEqual(false))
assertThat(c.isCompletedExceptionally, IsEqual(false))
- assertThrows<IllegalStateException> { c.getCompletionException() }
+ assertThrows<IllegalStateException> { c.getCancellationException() }
assertThrows<IllegalStateException> { c.getCompleted() }
+ assertThrows<IllegalStateException> { c.getCompletionExceptionOrNull() }
}
@Test
@@ -48,8 +50,9 @@
assertThat(c.isCancelled, IsEqual(false))
assertThat(c.isCompleted, IsEqual(true))
assertThat(c.isCompletedExceptionally, IsEqual(false))
- assertThat(c.getCompletionException(), IsInstanceOf(CancellationException::class.java))
+ assertThat(c.getCancellationException(), IsInstanceOf(JobCancellationException::class.java))
assertThat(c.getCompleted(), IsEqual("OK"))
+ assertThat(c.getCompletionExceptionOrNull(), IsNull())
}
@Test
@@ -66,8 +69,9 @@
assertThat(c.isCancelled, IsEqual(false))
assertThat(c.isCompleted, IsEqual(true))
assertThat(c.isCompletedExceptionally, IsEqual(true))
- assertThat(c.getCompletionException(), IsInstanceOf(TestException::class.java))
+ assertThat(c.getCancellationException(), IsInstanceOf(JobCancellationException::class.java))
assertThrows<TestException> { c.getCompleted() }
+ assertThat(c.getCompletionExceptionOrNull(), IsInstanceOf(TestException::class.java))
}
@Test
@@ -84,8 +88,9 @@
assertThat(c.isCancelled, IsEqual(true))
assertThat(c.isCompleted, IsEqual(true))
assertThat(c.isCompletedExceptionally, IsEqual(true))
- assertThat(c.getCompletionException(), IsInstanceOf(CancellationException::class.java))
+ assertThat(c.getCancellationException(), IsInstanceOf(CancellationException::class.java))
assertThrows<CancellationException> { c.getCompleted() }
+ assertThat(c.getCompletionExceptionOrNull(), IsInstanceOf(CancellationException::class.java))
}
@Test
@@ -102,8 +107,9 @@
assertThat(c.isCancelled, IsEqual(true))
assertThat(c.isCompleted, IsEqual(true))
assertThat(c.isCompletedExceptionally, IsEqual(true))
- assertThat(c.getCompletionException(), IsInstanceOf(TestException::class.java))
+ assertThat(c.getCancellationException(), IsInstanceOf(JobCancellationException::class.java))
assertThrows<TestException> { c.getCompleted() }
+ assertThat(c.getCompletionExceptionOrNull(), IsInstanceOf(TestException::class.java))
}
@Test
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt
similarity index 65%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt
index 22839e3..3408eeb 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt
@@ -21,20 +21,20 @@
class CoroutinesTest : TestBase() {
@Test
- fun testSimple() = runBlocking {
+ fun testSimple() = runTest {
expect(1)
finish(2)
}
@Test
- fun testYield() = runBlocking {
+ fun testYield() = runTest {
expect(1)
yield() // effectively does nothing, as we don't have other coroutines
finish(2)
}
@Test
- fun testLaunchAndYieldJoin() = runBlocking {
+ fun testLaunchAndYieldJoin() = runTest {
expect(1)
val job = launch(coroutineContext) {
expect(3)
@@ -49,7 +49,7 @@
}
@Test
- fun testLaunchUndispatched() = runBlocking {
+ fun testLaunchUndispatched() = runTest {
expect(1)
val job = launch(coroutineContext, start = CoroutineStart.UNDISPATCHED) {
expect(2)
@@ -64,7 +64,7 @@
}
@Test
- fun testNested() = runBlocking {
+ fun testNested() = runTest {
expect(1)
val j1 = launch(coroutineContext) {
expect(3)
@@ -81,20 +81,21 @@
}
@Test
- fun testCancelChildImplicit() = runBlocking {
+ fun testWaitChild() = runTest {
expect(1)
launch(coroutineContext) {
expect(3)
- yield() // parent finishes earlier, does not wait for us
- expectUnreached()
+ yield() // to parent
+ finish(5)
}
expect(2)
yield()
- finish(4)
+ expect(4)
+ // parent waits for child's completion
}
@Test
- fun testCancelChildExplicit() = runBlocking {
+ fun testCancelChildExplicit() = runTest {
expect(1)
val job = launch(coroutineContext) {
expect(3)
@@ -109,7 +110,7 @@
}
@Test
- fun testCancelChildWithFinally() = runBlocking {
+ fun testCancelChildWithFinally() = runTest {
expect(1)
val job = launch(coroutineContext) {
expect(3)
@@ -128,36 +129,42 @@
}
@Test
- fun testCancelNestedImplicit() = runBlocking {
+ fun testWaitNestedChild() = runTest {
expect(1)
launch(coroutineContext) {
expect(3)
launch(coroutineContext) {
expect(6)
- yield() // parent finishes earlier, does not wait for us
- expectUnreached()
+ yield() // to parent
+ expect(9)
}
expect(4)
yield()
expect(7)
- yield() // does not go further, because already cancelled
- expectUnreached()
+ yield() // to parent
+ finish(10) // the last one to complete
}
expect(2)
yield()
expect(5)
yield()
- finish(8)
+ expect(8)
+ // parent waits for child
}
- @Test(expected = IOException::class)
- fun testExceptionPropagation(): Unit = runBlocking {
+ @Test
+ fun testExceptionPropagation() = runTest(
+ expected = { it is IOException }
+ ) {
finish(1)
throw IOException()
}
- @Test(expected = IOException::class)
- fun testCancelParentOnChildException(): Unit = runBlocking {
+ @Test
+ fun testCancelParentOnChildException() = runTest(
+ expected = { it is IOException },
+ unhandled = listOf({ it -> it is IOException })
+ ) {
expect(1)
launch(coroutineContext) {
finish(3)
@@ -168,8 +175,14 @@
expectUnreached() // because of exception in child
}
- @Test(expected = IOException::class)
- fun testCancelParentOnNestedException(): Unit = runBlocking {
+ @Test
+ fun testCancelParentOnNestedException() = runTest(
+ expected = { it is IOException },
+ unhandled = listOf(
+ { it -> it is IOException },
+ { it -> it is IOException }
+ )
+ ) {
expect(1)
launch(coroutineContext) {
expect(3)
@@ -189,7 +202,7 @@
}
@Test
- fun testJoinWithFinally() = runBlocking {
+ fun testJoinWithFinally() = runTest {
expect(1)
val job = launch(coroutineContext) {
expect(3)
@@ -213,4 +226,44 @@
check(!job.isActive && job.isCompleted && job.isCancelled)
finish(9)
}
+
+ @Test
+ fun testCancelAndJoin() = runTest {
+ expect(1)
+ val job = launch(coroutineContext, CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ yield()
+ expectUnreached() // will get cancelled
+ } finally {
+ expect(4)
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(5)
+ }
+
+ @Test
+ fun testCancelAndJoinChildCrash() = runTest(
+ expected = { it is IOException && it.message == "OK" },
+ unhandled = listOf({it -> it is IOException })
+ ) {
+ expect(1)
+ val job = launch(coroutineContext, CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ throw IOException("OK")
+ }
+ // now we have a failed job with IOException
+ finish(3)
+ try {
+ job.cancelAndJoin() // join should crash on child's exception but it will be wrapped into JobCancellationException
+ } catch (e: Throwable) {
+ e as JobCancellationException // type assertion
+ check(e.cause is IOException)
+ check(e.job === coroutineContext[Job])
+ throw e
+ }
+ expectUnreached()
+ }
}
diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt
new file mode 100644
index 0000000..fe69e97
--- /dev/null
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.core.IsEqual
+import org.junit.Test
+import java.util.concurrent.Executor
+import java.util.concurrent.Executors
+import kotlin.coroutines.experimental.AbstractCoroutineContextElement
+import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.ContinuationInterceptor
+import kotlin.coroutines.experimental.CoroutineContext
+
+class DelayTest : TestBase() {
+ /**
+ * Test that delay works properly in contexts with custom [ContinuationInterceptor]
+ */
+ @Test
+ fun testDelayInArbitraryContext() = runBlocking {
+ var thread: Thread? = null
+ val pool = Executors.newFixedThreadPool(1) { runnable ->
+ Thread(runnable).also { thread = it }
+ }
+ val context = CustomInterceptor(pool)
+ val c = async(context) {
+ assertThat(Thread.currentThread(), IsEqual(thread))
+ delay(100)
+ assertThat(Thread.currentThread(), IsEqual(thread))
+ 42
+ }
+ assertThat(c.await(), IsEqual(42))
+ pool.shutdown()
+ }
+
+
+ @Test
+ fun testDelayWithoutDispatcher() = runBlocking(CoroutineName("testNoDispatcher.main")) {
+ // launch w/o a specified dispatcher
+ val c = async(CoroutineName("testNoDispatcher.inner")) {
+ delay(100)
+ 42
+ }
+ assertThat(c.await(), IsEqual(42))
+ }
+
+ class CustomInterceptor(val pool: Executor) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
+ override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
+ Wrapper(pool, continuation)
+ }
+
+ class Wrapper<T>(val pool: Executor, val cont: Continuation<T>) : Continuation<T> {
+ override val context: CoroutineContext
+ get() = cont.context
+
+ override fun resume(value: T) {
+ pool.execute { cont.resume(value) }
+ }
+
+ override fun resumeWithException(exception: Throwable) {
+ pool.execute { cont.resumeWithException(exception) }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/ExecutorsTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/ExecutorsTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/ExecutorsTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/ExecutorsTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobDisposeTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobDisposeTest.kt
similarity index 96%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobDisposeTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobDisposeTest.kt
index bbb5bda..2cc1399 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobDisposeTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobDisposeTest.kt
@@ -51,7 +51,7 @@
threads += testThread("creator") {
while (!done) {
val job = TestJob()
- val handle = job.invokeOnCompletion({ /* nothing */ }, onCancelling = true)
+ val handle = job.invokeOnCompletion(onCancelling = true) { /* nothing */ }
this.job = job // post job to cancelling thread
this.handle = handle // post handle to concurrent disposer thread
handle.dispose() // dispose of handle from this thread (concurrently with other disposer)
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt
similarity index 97%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt
index e8596d7..248c3b8 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt
@@ -117,7 +117,8 @@
val tryCancel = Try<Unit> { job.cancel() }
check(!job.isActive)
for (i in 0 until n) assertEquals(1, fireCount[i])
- check(tryCancel.exception is TestException)
+ check(tryCancel.exception is CompletionHandlerException)
+ check(tryCancel.exception!!.cause is TestException)
}
@Test
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/LaunchLazyTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/LaunchLazyTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/LaunchLazyTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/LaunchLazyTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/RunTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/RunTest.kt
similarity index 83%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/RunTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/RunTest.kt
index 0854209..87c36ec 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/RunTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/RunTest.kt
@@ -24,10 +24,10 @@
class RunTest : TestBase() {
@Test
- fun testSameContextNoSuspend() = runBlocking<Unit> {
+ fun testSameContextNoSuspend() = runTest {
expect(1)
launch(coroutineContext) { // make sure there is not early dispatch here
- expectUnreached() // will terminate before it has a chance to start
+ finish(5) // after main exits
}
expect(2)
val result = run(coroutineContext) { // same context!
@@ -35,11 +35,12 @@
"OK"
}
assertThat(result, IsEqual("OK"))
- finish(4)
+ expect(4)
+ // will wait for the first coroutine
}
@Test
- fun testSameContextWithSuspend() = runBlocking<Unit> {
+ fun testSameContextWithSuspend() = runTest {
expect(1)
launch(coroutineContext) { // make sure there is not early dispatch here
expect(4)
@@ -56,10 +57,10 @@
}
@Test
- fun testCancelWithJobNoSuspend() = runBlocking<Unit> {
+ fun testCancelWithJobNoSuspend() = runTest {
expect(1)
launch(coroutineContext) { // make sure there is not early dispatch to here
- expectUnreached() // will terminate before it has a chance to start
+ finish(6) // after main exits
}
expect(2)
val job = Job()
@@ -75,11 +76,12 @@
"OK"
}
assertThat(result, IsEqual("OK"))
- finish(5)
+ expect(5)
+ // will wait for the first coroutine
}
@Test
- fun testCancelWithJobWithSuspend() = runBlocking<Unit> {
+ fun testCancelWithJobWithSuspend() = runTest {
expect(1)
launch(coroutineContext) { // make sure there is not early dispatch to here
expect(4)
@@ -104,7 +106,7 @@
}
@Test
- fun testCommonPoolNoSuspend() = runBlocking<Unit> {
+ fun testCommonPoolNoSuspend() = runTest {
expect(1)
val result = run(CommonPool) {
expect(2)
@@ -115,7 +117,7 @@
}
@Test
- fun testCommonPoolWithSuspend() = runBlocking<Unit> {
+ fun testCommonPoolWithSuspend() = runTest {
expect(1)
val result = run(CommonPool) {
expect(2)
@@ -127,8 +129,10 @@
finish(4)
}
- @Test(expected = CancellationException::class)
- fun testRunCancellableDefault() = runBlocking<Unit> {
+ @Test
+ fun testRunCancellableDefault() = runTest(
+ expected = { it is JobCancellationException }
+ ) {
val job = Job()
job.cancel() // cancel before it has a chance to run
run(job + wrapperDispatcher(coroutineContext)) {
@@ -136,8 +140,10 @@
}
}
- @Test(expected = CancellationException::class)
- fun testRunAtomicTryCancel() = runBlocking<Unit> {
+ @Test
+ fun testRunAtomicTryCancel() = runTest(
+ expected = { it is JobCancellationException }
+ ) {
expect(1)
val job = Job()
job.cancel() // try to cancel before it has a chance to run
@@ -148,8 +154,10 @@
}
}
- @Test(expected = CancellationException::class)
- fun testRunUndispatchedTryCancel() = runBlocking<Unit> {
+ @Test
+ fun testRunUndispatchedTryCancel() = runTest(
+ expected = { it is JobCancellationException }
+ ) {
expect(1)
val job = Job()
job.cancel() // try to cancel before it has a chance to run
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt
similarity index 75%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt
index f4cd14a..8327d37 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt
@@ -62,8 +62,8 @@
* Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
* complete successfully even if this exception is consumed somewhere in the test.
*/
- public fun error(message: Any): Nothing {
- val exception = IllegalStateException(message.toString())
+ public fun error(message: Any, cause: Throwable? = null): Nothing {
+ val exception = IllegalStateException(message.toString(), cause)
error.compareAndSet(null, exception)
throw exception
}
@@ -116,4 +116,35 @@
DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT)
checkTestThreads(threadsBefore)
}
+
+ fun runTest(
+ expected: ((Throwable) -> Boolean)? = null,
+ unhandled: List<(Throwable) -> Boolean> = emptyList(),
+ block: suspend CoroutineScope.() -> Unit
+ ) {
+ var exCount = 0
+ var ex: Throwable? = null
+ try {
+ runBlocking(block = block, context = CoroutineExceptionHandler { context, e ->
+ if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
+ exCount++
+ if (exCount > unhandled.size)
+ error("Too many unhandled exceptions $exCount, expected ${unhandled.size}, got: $e", e)
+ if (!unhandled[exCount - 1](e))
+ error("Unhandled exception was unexpected: $e", e)
+ context[Job]?.cancel(e)
+ })
+ } catch (e: Throwable) {
+ ex = e
+ if (expected != null) {
+ if (!expected(e))
+ error("Unexpected exception: $e", e)
+ } else
+ throw e
+ } finally {
+ if (ex == null && expected != null) error("Exception was expected but none produced")
+ }
+ if (unhandled != null && exCount < unhandled.size)
+ error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
+ }
}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestSecurityManager.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestSecurityManager.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestSecurityManager.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestSecurityManager.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/Try.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/Try.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/Try.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/Try.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
similarity index 79%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
index 4b0436f..540e0b6 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
@@ -24,10 +24,40 @@
class WithTimeoutOrNullTest : TestBase() {
/**
+ * Tests a case of no timeout and no suspension inside.
+ */
+ @Test
+ fun testBasicNoSuspend() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(10_000) {
+ expect(2)
+ "OK"
+ }
+ assertThat(result, IsEqual("OK"))
+ finish(3)
+ }
+
+ /**
+ * Tests a case of no timeout and one suspension inside.
+ */
+ @Test
+ fun testBasicSuspend() = runTest {
+ expect(1)
+ val result = withTimeoutOrNull(10_000) {
+ expect(2)
+ yield()
+ expect(3)
+ "OK"
+ }
+ assertThat(result, IsEqual("OK"))
+ finish(4)
+ }
+
+ /**
* Tests property dispatching of `withTimeoutOrNull` blocks
*/
@Test
- fun testDispatch() = runBlocking {
+ fun testDispatch() = runTest {
expect(1)
launch(coroutineContext) {
expect(4)
@@ -49,7 +79,7 @@
}
@Test
- fun testNullOnTimeout() = runBlocking {
+ fun testNullOnTimeout() = runTest {
expect(1)
val result = withTimeoutOrNull(100) {
expect(2)
@@ -62,7 +92,7 @@
}
@Test
- fun testSuppressException() = runBlocking {
+ fun testSuppressException() = runTest {
expect(1)
val result = withTimeoutOrNull(100) {
expect(2)
@@ -73,24 +103,28 @@
}
"OK"
}
- assertThat(result, IsEqual("OK"))
+ assertThat(result, IsNull())
finish(4)
}
- @Test(expected = IOException::class)
- fun testReplaceException() = runBlocking {
+ @Test
+ fun testReplaceException() = runTest(
+ unhandled = listOf({ it -> it is UnexpectedCoroutineException && it.cause is IOException })
+ ) {
expect(1)
- withTimeoutOrNull(100) {
+ val result = withTimeoutOrNull(100) {
expect(2)
try {
delay(1000)
} catch (e: CancellationException) {
- finish(3)
+ expect(3)
throw IOException(e)
}
+ expectUnreached()
"OK"
}
- expectUnreached()
+ assertThat(result, IsNull())
+ finish(4)
}
/**
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullThreadDispatchTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullThreadDispatchTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullThreadDispatchTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullThreadDispatchTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
similarity index 62%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
index f0f6ebd..b4bfdff 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
@@ -23,10 +23,40 @@
class WithTimeoutTest : TestBase() {
/**
+ * Tests a case of no timeout and no suspension inside.
+ */
+ @Test
+ fun testBasicNoSuspend() = runTest {
+ expect(1)
+ val result = withTimeout(10_000) {
+ expect(2)
+ "OK"
+ }
+ assertThat(result, IsEqual("OK"))
+ finish(3)
+ }
+
+ /**
+ * Tests a case of no timeout and one suspension inside.
+ */
+ @Test
+ fun testBasicSuspend() = runTest {
+ expect(1)
+ val result = withTimeout(10_000) {
+ expect(2)
+ yield()
+ expect(3)
+ "OK"
+ }
+ assertThat(result, IsEqual("OK"))
+ finish(4)
+ }
+
+ /**
* Tests proper dispatching of `withTimeout` blocks
*/
@Test
- fun testDispatch() = runBlocking {
+ fun testDispatch() = runTest {
expect(1)
launch(coroutineContext) {
expect(4)
@@ -49,7 +79,7 @@
@Test
- fun testExceptionOnTimeout() = runBlocking<Unit> {
+ fun testExceptionOnTimeout() = runTest {
expect(1)
try {
withTimeout(100) {
@@ -65,23 +95,27 @@
}
@Test
- fun testSuppressException() = runBlocking {
+ fun testSuppressException() = runTest(
+ expected = { it is CancellationException }
+ ) {
expect(1)
val result = withTimeout(100) {
expect(2)
try {
delay(1000)
} catch (e: CancellationException) {
- expect(3)
+ finish(3)
}
"OK"
}
- assertThat(result, IsEqual("OK"))
- finish(4)
+ expectUnreached()
}
- @Test(expected = IOException::class)
- fun testReplaceException() = runBlocking {
+ @Test
+ fun testReplaceException() = runTest(
+ expected = { it is CancellationException },
+ unhandled = listOf({ it -> it is UnexpectedCoroutineException && it.cause is IOException })
+ ) {
expect(1)
withTimeout(100) {
expect(2)
@@ -91,6 +125,7 @@
finish(3)
throw IOException(e)
}
+ expectUnreached()
"OK"
}
expectUnreached()
@@ -100,11 +135,29 @@
* Tests that a 100% CPU-consuming loop will react on timeout if it has yields.
*/
@Test(expected = CancellationException::class)
- fun testYieldBlockingWithTimeout() = runBlocking {
+ fun testYieldBlockingWithTimeout() = runTest {
withTimeout(100) {
while (true) {
yield()
}
}
}
+
+ /**
+ * Tests that [withTimeout] waits for children coroutines to complete.
+ */
+ @Test
+ fun testWithTimeoutChildWait() = runTest {
+ expect(1)
+ withTimeout(100) {
+ expect(2)
+ // launch child with timeout
+ launch(coroutineContext) {
+ expect(4)
+ }
+ expect(3)
+ // now will wait for child before returning
+ }
+ finish(5)
+ }
}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutThreadDispatchTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutThreadDispatchTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutThreadDispatchTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutThreadDispatchTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ActorLazyTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ActorLazyTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ActorLazyTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ActorLazyTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ActorTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ActorTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ActorTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ActorTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelAtomicCancelStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelAtomicCancelStressTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelAtomicCancelStressTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelAtomicCancelStressTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelSendReceiveStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelSendReceiveStressTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelSendReceiveStressTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelSendReceiveStressTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelNotifyStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelNotifyStressTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelNotifyStressTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelNotifyStressTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelCloseStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelCloseStressTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelCloseStressTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelCloseStressTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/DoubleChannelCloseStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/DoubleChannelCloseStressTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/DoubleChannelCloseStressTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/DoubleChannelCloseStressTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt
diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt
new file mode 100644
index 0000000..f8eab64
--- /dev/null
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental.channels
+
+import kotlinx.coroutines.experimental.TestBase
+import kotlinx.coroutines.experimental.runBlocking
+import org.junit.Test
+
+class ProduceTest : TestBase() {
+ @Test
+ fun testBasic() = runBlocking {
+ val c = produce(coroutineContext) {
+ send(1)
+ send(2)
+ }
+ check(c.receive() == 1)
+ check(c.receive() == 2)
+ check(c.receiveOrNull() == null)
+ }
+
+ @Test
+ fun testCancel() = runBlocking {
+ val c = produce(coroutineContext) {
+ send(1)
+ send(2)
+ expectUnreached()
+ }
+ check(c.receive() == 1)
+ c.cancel()
+ check(c.receiveOrNull() == null)
+ }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt
similarity index 88%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt
index f940c1b..f0b55c2 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt
@@ -16,7 +16,7 @@
package kotlinx.coroutines.experimental.channels
-import kotlinx.coroutines.experimental.selects.SelectInstance
+import kotlinx.coroutines.experimental.selects.SelectClause1
enum class TestChannelKind {
RENDEZVOUS {
@@ -70,8 +70,8 @@
suspend override fun receiveOrNull(): E? = sub.receiveOrNull()
override fun poll(): E? = sub.poll()
override fun iterator(): ChannelIterator<E> = sub.iterator()
- override fun <R> registerSelectReceive(select: SelectInstance<R>, block: suspend (E) -> R) =
- sub.registerSelectReceive(select, block)
- override fun <R> registerSelectReceiveOrNull(select: SelectInstance<R>, block: suspend (E?) -> R) =
- sub.registerSelectReceiveOrNull(select, block)
+ override val onReceive: SelectClause1<E>
+ get() = sub.onReceive
+ override val onReceiveOrNull: SelectClause1<E?>
+ get() = sub.onReceiveOrNull
}
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListAtomicStressLFTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListAtomicStressLFTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListAtomicStressLFTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListAtomicStressLFTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListLongStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListLongStressTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListLongStressTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListLongStressTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListShortStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListShortStressTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListShortStressTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListShortStressTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedListTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeListLinearizabilityTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeListLinearizabilityTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeListLinearizabilityTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/LockFreeListLinearizabilityTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/ThreadSafeHeapTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/ThreadSafeHeapTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/ThreadSafeHeapTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/internal/ThreadSafeHeapTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt
similarity index 87%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt
index 99615b2..cbf1e6c 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt
@@ -128,4 +128,23 @@
finish(9)
}
+ @Test
+ fun testSelectCancel() = runTest(
+ expected = { it is JobCancellationException }
+ ) {
+ expect(1)
+ val d = CompletableDeferred<String>()
+ launch (coroutineContext) {
+ finish(3)
+ d.cancel() // will cancel after select starts
+ }
+ expect(2)
+ select<Unit> {
+ d.onAwait {
+ expectUnreached() // will not select
+ }
+ }
+ expectUnreached()
+ }
+
}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt
similarity index 88%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt
index 02f8f4a..9d38aaf 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt
@@ -22,10 +22,10 @@
class SelectJobTest : TestBase() {
@Test
- fun testSelectCompleted() = runBlocking<Unit> {
+ fun testSelectCompleted() = runTest {
expect(1)
launch(coroutineContext) { // makes sure we don't yield to it earlier
- expectUnreached() // will terminate before it has a chance to start
+ finish(4) // after main exits
}
val job = Job()
job.cancel()
@@ -34,11 +34,12 @@
expect(2)
}
}
- finish(3)
+ expect(3)
+ // will wait for the first coroutine
}
@Test
- fun testSelectIncomplete() = runBlocking<Unit> {
+ fun testSelectIncomplete() = runTest {
expect(1)
val job = Job()
launch(coroutineContext) { // makes sure we don't yield to it earlier
@@ -62,7 +63,7 @@
}
@Test
- fun testSelectLazy() = runBlocking<Unit> {
+ fun testSelectLazy() = runTest {
expect(1)
val job = launch(coroutineContext, CoroutineStart.LAZY) {
expect(2)
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt
similarity index 89%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt
index 0c0fb5a..4f60faf 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt
+++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt
@@ -18,7 +18,6 @@
import kotlinx.coroutines.experimental.TestBase
import kotlinx.coroutines.experimental.launch
-import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.experimental.sync.Mutex
import kotlinx.coroutines.experimental.sync.MutexImpl
import kotlinx.coroutines.experimental.yield
@@ -28,11 +27,11 @@
class SelectMutexTest : TestBase() {
@Test
- fun testSelectLock() = runBlocking<Unit> {
+ fun testSelectLock() = runTest {
val mutex = Mutex()
expect(1)
launch(coroutineContext) { // ensure that it is not scheduled earlier than needed
- expectUnreached() // will terminate before it has a chance to start
+ finish(4) // after main exits
}
val res = select<String> {
mutex.onLock {
@@ -42,11 +41,12 @@
}
}
assertEquals("OK", res)
- finish(3)
+ expect(3)
+ // will wait for the first coroutine
}
@Test
- fun testSelectLockWait() = runBlocking<Unit> {
+ fun testSelectLockWait() = runTest {
val mutex = Mutex(true) // locked
expect(1)
launch(coroutineContext) {
@@ -71,7 +71,7 @@
}
@Test
- fun testSelectCancelledResourceRelease() = runBlocking<Unit> {
+ fun testSelectCancelledResourceRelease() = runTest {
val n = 1_000 * stressTestMultiplier
val mutex = Mutex(true) as MutexImpl // locked
expect(1)
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectPhilosophersStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectPhilosophersStressTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectPhilosophersStressTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectPhilosophersStressTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt
similarity index 100%
rename from kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt
rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt
diff --git a/core/kotlinx-coroutines-io/README.md b/core/kotlinx-coroutines-io/README.md
new file mode 100644
index 0000000..03a32b2
--- /dev/null
+++ b/core/kotlinx-coroutines-io/README.md
@@ -0,0 +1,16 @@
+# Module kotlinx-coroutines-io
+
+Byte I/O channels (_unstable_, work in progress).
+
+# Package kotlinx.coroutines.experimental.io
+
+Byte I/O channels (_unstable_, work in progress).
+
+# Package kotlinx.coroutines.experimental.io.packet
+
+Byte I/O packets (_unstable_, work in progress).
+
+<!--- MODULE kotlinx-coroutines-io -->
+<!--- INDEX kotlinx.coroutines.experimental.io -->
+<!--- INDEX kotlinx.coroutines.experimental.io.packet -->
+<!--- END -->
diff --git a/core/kotlinx-coroutines-io/pom.xml b/core/kotlinx-coroutines-io/pom.xml
new file mode 100644
index 0000000..ed1abf9
--- /dev/null
+++ b/core/kotlinx-coroutines-io/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>kotlinx-coroutines</artifactId>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <version>0.19-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>kotlinx-coroutines-io</artifactId>
+ <packaging>jar</packaging>
+
+ <properties>
+ <subdir>core</subdir>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-coroutines-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlin</groupId>
+ <artifactId>kotlin-test-junit</artifactId>
+ <version>${kotlin.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-coroutines-core</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ <classifier>tests</classifier>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src/main/kotlin</sourceDirectory>
+ <testSourceDirectory>src/test/kotlin</testSourceDirectory>
+
+ <plugins>
+ <!-- documentation -->
+ <plugin>
+ <groupId>org.jetbrains.dokka</groupId>
+ <artifactId>dokka-maven-plugin</artifactId>
+ <version>${dokka.version}</version>
+ <configuration>
+ <externalDocumentationLinks combine.children="append">
+ <link>
+ <url>${core.docs.url}</url>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
+ </link>
+ </externalDocumentationLinks>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteBuffer.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteBuffer.kt
new file mode 100644
index 0000000..42eecf4
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteBuffer.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental.io
+
+/**
+ * Byte buffer.
+ */
+typealias ByteBuffer = java.nio.ByteBuffer
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannel.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannel.kt
new file mode 100644
index 0000000..e4c7df8
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannel.kt
@@ -0,0 +1,1418 @@
+@file:Suppress("UsePropertyAccessSyntax") // for ByteBuffer.getShort/getInt/etc
+
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.CancellableContinuation
+import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException
+import kotlinx.coroutines.experimental.io.internal.*
+import kotlinx.coroutines.experimental.io.packet.*
+import kotlinx.coroutines.experimental.suspendCancellableCoroutine
+import java.nio.BufferOverflowException
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
+
+internal const val DEFAULT_CLOSE_MESSAGE = "Byte channel was closed"
+
+// implementation for ByteChannel
+internal class ByteBufferChannel(
+ override val autoFlush: Boolean,
+ private val pool: ObjectPool<ReadWriteBufferState.Initial> = BufferObjectPool,
+ private val reservedSize: Int = RESERVED_SIZE
+) : ByteChannel, LookAheadSuspendSession {
+ // internal constructor for reading of byte buffers
+ constructor(content: ByteBuffer) : this(false, BufferObjectNoPool, 0) {
+ state = ReadWriteBufferState.Initial(content.slice(), 0).apply {
+ capacity.resetForRead()
+ }.startWriting()
+ restoreStateAfterWrite()
+ close()
+ tryTerminate()
+ }
+
+ @Volatile
+ private var state: ReadWriteBufferState = ReadWriteBufferState.IdleEmpty
+
+ @Volatile
+ private var closed: ClosedElement? = null
+
+ @Volatile
+ private var readOp: CancellableContinuation<Boolean>? = null
+
+ @Volatile
+ private var writeOp: CancellableContinuation<Unit>? = null
+
+ private var readPosition = 0
+ private var writePosition = 0
+
+ override var readByteOrder: ByteOrder = ByteOrder.BIG_ENDIAN
+ override var writeByteOrder: ByteOrder = ByteOrder.BIG_ENDIAN
+
+ override val availableForRead: Int
+ get() = state.capacity.availableForRead
+
+ override val availableForWrite: Int
+ get() = state.capacity.availableForWrite
+
+ override val isClosedForRead: Boolean
+ get() = state === ReadWriteBufferState.Terminated
+
+ override val isClosedForWrite: Boolean
+ get() = closed != null
+
+ @Volatile
+ override var totalBytesRead: Long = 0L
+ private set
+
+ @Volatile
+ override var totalBytesWritten: Long = 0L
+ private set
+
+ override fun close(cause: Throwable?): Boolean {
+ if (closed != null) return false
+ val newClosed = if (cause == null) ClosedElement.EmptyCause else ClosedElement(cause)
+ if (!Closed.compareAndSet(this, null, newClosed)) return false
+ state.capacity.flush()
+ if (state.capacity.isEmpty() || cause != null) tryTerminate()
+ resumeClosed(cause)
+ return true
+ }
+
+ override fun flush() {
+ if (!state.capacity.flush()) return
+ resumeReadOp()
+ if (availableForWrite > 0) resumeWriteOp()
+ }
+
+ private fun ByteBuffer.prepareBuffer(order: ByteOrder, position: Int, available: Int) {
+ require(position >= 0)
+ require(available >= 0)
+
+ val bufferLimit = capacity() - reservedSize
+ val virtualLimit = position + available
+
+ order(order)
+ limit(virtualLimit.coerceAtMost(bufferLimit))
+ position(position)
+ }
+
+ private fun setupStateForWrite(): ByteBuffer {
+ var _allocated: ReadWriteBufferState.Initial? = null
+ val (old, newState) = updateState { state ->
+ when (state) {
+ ReadWriteBufferState.IdleEmpty -> {
+ val allocated = _allocated ?: newBuffer().also { _allocated = it }
+ allocated.startWriting()
+ }
+ ReadWriteBufferState.Terminated -> throw closed!!.sendException
+ else -> {
+ state.startWriting()
+ }
+ }
+ }
+ val buffer = newState.writeBuffer
+
+ _allocated?.let { allocated ->
+ if (old !== ReadWriteBufferState.IdleEmpty) {
+ releaseBuffer(allocated)
+ }
+ }
+ return buffer.apply {
+ prepareBuffer(writeByteOrder, writePosition, newState.capacity.availableForWrite)
+ }
+ }
+
+ private fun restoreStateAfterWrite() {
+ var toRelease: ReadWriteBufferState.IdleNonEmpty? = null
+
+ val (_, newState) = updateState {
+ val writeStopped = it.stopWriting()
+ if (writeStopped is ReadWriteBufferState.IdleNonEmpty && writeStopped.capacity.isEmpty()) {
+ toRelease = writeStopped
+ ReadWriteBufferState.IdleEmpty
+ } else {
+ writeStopped
+ }
+ }
+
+ if (newState === ReadWriteBufferState.IdleEmpty) {
+ toRelease?.let { releaseBuffer(it.initial) }
+ }
+ }
+
+ private fun setupStateForRead(): ByteBuffer? {
+ val (_, newState) = updateState { state ->
+ when (state) {
+ ReadWriteBufferState.Terminated -> closed!!.cause?.let { throw it } ?: return null
+ ReadWriteBufferState.IdleEmpty -> closed?.cause?.let { throw it } ?: return null
+ else -> {
+ if (state.capacity.availableForRead == 0) return null
+ state.startReading()
+ }
+ }
+ }
+
+ return newState.readBuffer.apply {
+ prepareBuffer(readByteOrder, readPosition, newState.capacity.availableForRead)
+ }
+ }
+
+ private fun restoreStateAfterRead() {
+ var toRelease: ReadWriteBufferState.IdleNonEmpty? = null
+
+ val (_, newState) = updateState { state ->
+ toRelease?.let {
+ it.capacity.resetForWrite()
+ resumeWriteOp()
+ toRelease = null
+ }
+
+ val readStopped = state.stopReading()
+
+ if (readStopped is ReadWriteBufferState.IdleNonEmpty) {
+ if (this.state === state && readStopped.capacity.tryLockForRelease()) {
+ toRelease = readStopped
+ ReadWriteBufferState.IdleEmpty
+ } else {
+ readStopped
+ }
+ } else {
+ readStopped
+ }
+ }
+
+ if (newState === ReadWriteBufferState.IdleEmpty) {
+ toRelease?.let { releaseBuffer(it.initial) }
+ resumeWriteOp()
+ }
+ }
+
+ private fun tryTerminate() {
+ val closed = closed ?: return
+ var toRelease: ReadWriteBufferState.Initial? = null
+
+ updateState { state ->
+ when {
+ state === ReadWriteBufferState.IdleEmpty -> ReadWriteBufferState.Terminated
+ closed.cause != null && state is ReadWriteBufferState.IdleNonEmpty -> {
+ toRelease = state.initial
+ ReadWriteBufferState.Terminated
+ }
+ else -> return
+ }
+ }
+
+ toRelease?.let { buffer ->
+ if (state === ReadWriteBufferState.Terminated) {
+ releaseBuffer(buffer)
+ }
+ }
+
+ WriteOp.getAndSet(this, null)?.resumeWithException(closed.sendException)
+ ReadOp.getAndSet(this, null)?.apply {
+ if (closed.cause != null) resumeWithException(closed.cause) else resume(false)
+ }
+ }
+
+ private fun ByteBuffer.carryIndex(idx: Int) = if (idx >= capacity() - reservedSize) idx - (capacity() - reservedSize) else idx
+
+ private inline fun writing(block: ByteBuffer.(RingBufferCapacity) -> Unit) {
+ val buffer = setupStateForWrite()
+ val capacity = state.capacity
+ try {
+ closed?.let { throw it.sendException }
+ block(buffer, capacity)
+ } finally {
+ if (capacity.isFull() || autoFlush) flush()
+ restoreStateAfterWrite()
+ tryTerminate()
+ }
+ }
+
+ private inline fun reading(block: ByteBuffer.(RingBufferCapacity) -> Boolean): Boolean {
+ val buffer = setupStateForRead() ?: return false
+ val capacity = state.capacity
+ try {
+ if (capacity.availableForRead == 0) return false
+
+ return block(buffer, capacity)
+ } finally {
+ restoreStateAfterRead()
+ tryTerminate()
+ }
+ }
+
+ private tailrec fun readAsMuchAsPossible(dst: ByteBuffer, consumed0: Int = 0): Int {
+ var consumed = 0
+
+ val rc = reading {
+ val part = it.tryReadAtMost(minOf(remaining(), dst.remaining()))
+ if (part > 0) {
+ consumed += part
+
+ if (dst.remaining() >= remaining()) {
+ dst.put(this)
+ } else {
+ repeat(part) {
+ dst.put(get())
+ }
+ }
+
+ bytesRead(it, part)
+ true
+ } else {
+ false
+ }
+ }
+
+ return if (rc && dst.hasRemaining() && state.capacity.availableForRead > 0)
+ readAsMuchAsPossible(dst, consumed0 + consumed)
+ else consumed + consumed0
+ }
+
+ private tailrec fun readAsMuchAsPossible(dst: ByteArray, offset: Int, length: Int, consumed0: Int = 0): Int {
+ var consumed = 0
+
+ val rc = reading {
+ val part = it.tryReadAtMost(minOf(remaining(), length))
+ if (part > 0) {
+ consumed += part
+ get(dst, offset, part)
+
+ bytesRead(it, part)
+ true
+ } else {
+ false
+ }
+ }
+
+ return if (rc && consumed < length && state.capacity.availableForRead > 0)
+ readAsMuchAsPossible(dst, offset + consumed, length - consumed, consumed0 + consumed)
+ else consumed + consumed0
+ }
+
+ suspend override fun readFully(dst: ByteArray, offset: Int, length: Int) {
+ val consumed = readAsMuchAsPossible(dst, offset, length)
+
+ if (consumed < length) {
+ readFullySuspend(dst, offset + consumed, length - consumed)
+ }
+ }
+
+ suspend override fun readFully(dst: ByteBuffer): Int {
+ val rc = readAsMuchAsPossible(dst)
+ if (!dst.hasRemaining()) return rc
+
+ return readFullySuspend(dst, rc)
+ }
+
+ private suspend fun readFullySuspend(dst: ByteBuffer, rc0: Int): Int {
+ var copied = rc0
+
+ while (dst.hasRemaining()) {
+ if (!readSuspend(1)) throw ClosedReceiveChannelException("Unexpected EOF: expected ${dst.remaining()} more bytes")
+ copied += readAsMuchAsPossible(dst)
+ }
+
+ return copied
+ }
+
+ private suspend tailrec fun readFullySuspend(dst: ByteArray, offset: Int, length: Int) {
+ if (!readSuspend(1)) throw ClosedReceiveChannelException("Unexpected EOF: expected $length more bytes")
+
+ val consumed = readAsMuchAsPossible(dst, offset, length)
+
+ if (consumed < length) {
+ readFullySuspend(dst, offset + consumed, length - consumed)
+ }
+ }
+
+ suspend override fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int {
+ val consumed = readAsMuchAsPossible(dst, offset, length)
+
+ return when {
+ consumed == 0 && closed != null -> {
+ if (state.capacity.flush()) {
+ return readAsMuchAsPossible(dst, offset, length)
+ } else {
+ -1
+ }
+ }
+ consumed > 0 || length == 0 -> consumed
+ else -> readAvailableSuspend(dst, offset, length)
+ }
+ }
+
+ suspend override fun readAvailable(dst: ByteBuffer): Int {
+ val consumed = readAsMuchAsPossible(dst)
+
+ return when {
+ consumed == 0 && closed != null -> {
+ if (state.capacity.flush()) {
+ return readAsMuchAsPossible(dst)
+ } else {
+ -1
+ }
+ }
+ consumed > 0 || !dst.hasRemaining() -> consumed
+ else -> readAvailableSuspend(dst)
+ }
+ }
+
+ private suspend fun readAvailableSuspend(dst: ByteArray, offset: Int, length: Int): Int {
+ if (!readSuspend(1)) return -1
+ return readAvailable(dst, offset, length)
+ }
+
+ private suspend fun readAvailableSuspend(dst: ByteBuffer): Int {
+ if (!readSuspend(1)) return -1
+ return readAvailable(dst)
+ }
+
+ suspend override fun readPacket(size: Int): ByteReadPacket {
+ closed?.cause?.let { throw it }
+
+ if (size == 0) return ByteReadPacketEmpty
+
+ val builder = ByteWritePacketImpl(BufferPool)
+ val buffer = BufferPool.borrow()
+
+ try {
+ var remaining = size
+ while (remaining > 0) {
+ buffer.clear()
+ if (buffer.remaining() > remaining) {
+ buffer.limit(remaining)
+ }
+
+ val rc = readFully(buffer)
+ buffer.flip()
+ builder.writeFully(buffer)
+
+ remaining -= rc
+ }
+
+ return builder.build()
+ } catch (t: Throwable) {
+ builder.release()
+ throw t
+ } finally {
+ BufferPool.recycle(buffer)
+ }
+ }
+
+ suspend override fun readByte(): Byte {
+ var b: Byte = 0
+
+ val rc = reading {
+ if (it.tryReadExact(1)) {
+ b = get()
+ bytesRead(it, 1)
+ true
+ } else false
+ }
+
+ return if (rc) {
+ b
+ } else {
+ readByteSuspend()
+ }
+ }
+
+ private suspend fun readByteSuspend(): Byte {
+ if (!readSuspend(1)) throw ClosedReceiveChannelException("EOF: one byte required")
+ return readByte()
+ }
+
+ suspend override fun readBoolean(): Boolean {
+ var b = false
+
+ val rc = reading {
+ if (it.tryReadExact(1)) {
+ b = get() != 0.toByte()
+ bytesRead(it, 1)
+ true
+ } else false
+ }
+
+ return if (rc) {
+ b
+ } else {
+ readBooleanSuspend()
+ }
+ }
+
+ private suspend fun readBooleanSuspend(): Boolean {
+ if (!readSuspend(1)) throw ClosedReceiveChannelException("EOF: one byte required")
+ return readBoolean()
+ }
+
+ suspend override fun readShort(): Short {
+ var sh: Short = 0
+
+ val rc = reading {
+ if (it.tryReadExact(2)) {
+ if (remaining() < 2) rollBytes(2)
+ sh = getShort()
+ bytesRead(it, 2)
+ true
+ } else false
+ }
+
+ return if (rc) {
+ sh
+ } else {
+ readShortSuspend()
+ }
+ }
+
+ private suspend fun readShortSuspend(): Short {
+ if (!readSuspend(2)) throw ClosedReceiveChannelException("EOF while byte expected")
+ return readShort()
+ }
+
+ suspend override fun readInt(): Int {
+ var i = 0
+
+ val rc = reading {
+ if (it.tryReadExact(4)) {
+ if (remaining() < 4) rollBytes(4)
+ i = getInt()
+ bytesRead(it, 4)
+ true
+ } else false
+ }
+
+ return if (rc) {
+ i
+ } else {
+ readIntSuspend()
+ }
+ }
+
+ private suspend fun readIntSuspend(): Int {
+ if (!readSuspend(4)) throw ClosedReceiveChannelException("EOF while an int expected")
+ return readInt()
+ }
+
+ suspend override fun readLong(): Long {
+ var i = 0L
+
+ val rc = reading {
+ if (it.tryReadExact(8)) {
+ if (remaining() < 8) rollBytes(8)
+ i = getLong()
+ bytesRead(it, 8)
+ true
+ } else false
+ }
+
+ return if (rc) {
+ i
+ } else {
+ readLongSuspend()
+ }
+ }
+
+ private suspend fun readLongSuspend(): Long {
+ if (!readSuspend(8)) throw ClosedReceiveChannelException("EOF while a long expected")
+ return readLong()
+ }
+
+ suspend override fun readDouble(): Double {
+ var d = 0.0
+
+ val rc = reading {
+ if (it.tryReadExact(8)) {
+ if (remaining() < 8) rollBytes(8)
+ d = getDouble()
+ bytesRead(it, 8)
+ true
+ } else false
+ }
+
+ return if (rc) {
+ d
+ } else {
+ readDoubleSuspend()
+ }
+ }
+
+ private suspend fun readDoubleSuspend(): Double {
+ if (!readSuspend(8)) throw ClosedReceiveChannelException("EOF while a double expected")
+ return readDouble()
+ }
+
+ suspend override fun readFloat(): Float {
+ var f = 0.0f
+
+ val rc = reading {
+ if (it.tryReadExact(4)) {
+ if (remaining() < 4) rollBytes(4)
+ f = getFloat()
+ bytesRead(it, 4)
+ true
+ } else false
+ }
+
+ return if (rc) {
+ f
+ } else {
+ readFloatSuspend()
+ }
+ }
+
+ private suspend fun readFloatSuspend(): Float {
+ if (!readSuspend(4)) throw ClosedReceiveChannelException("EOF while an int expected")
+ return readFloat()
+ }
+
+ private fun ByteBuffer.rollBytes(n: Int) {
+ limit(position() + n)
+ for (i in 1..n - remaining()) {
+ put(capacity() + ReservedLongIndex + i, get(i))
+ }
+ }
+
+ private fun ByteBuffer.carry() {
+ val base = capacity() - reservedSize
+ for (i in base until position()) {
+ put(i - base, get(i))
+ }
+ }
+
+ private fun ByteBuffer.bytesWritten(c: RingBufferCapacity, n: Int) {
+ require(n >= 0)
+
+ writePosition = carryIndex(writePosition + n)
+ c.completeWrite(n)
+ totalBytesWritten += n
+ }
+
+ private fun ByteBuffer.bytesRead(c: RingBufferCapacity, n: Int) {
+ require(n >= 0)
+
+ readPosition = carryIndex(readPosition + n)
+ c.completeRead(n)
+ totalBytesRead += n
+ resumeWriteOp()
+ }
+
+ suspend override fun writeByte(b: Byte) {
+ writing {
+ tryWriteByte(b, it)
+ }
+ }
+
+ private suspend fun ByteBuffer.tryWriteByte(b: Byte, c: RingBufferCapacity) {
+ if (c.tryWriteExact(1)) {
+ put(b)
+ bytesWritten(c, 1)
+ } else {
+ writeByteSuspend(b, c)
+ }
+ }
+
+ private suspend fun ByteBuffer.writeByteSuspend(b: Byte, c: RingBufferCapacity) {
+ writeSuspend(1)
+ tryWriteByte(b, c)
+ }
+
+ suspend override fun writeShort(s: Short) {
+ writing {
+ if (!tryWriteShort(s, it)) {
+ writeShortSuspend(s, it)
+ }
+ }
+ }
+
+ private suspend fun ByteBuffer.writeShortSuspend(s: Short, c: RingBufferCapacity) {
+ writeSuspend(2)
+ tryWriteShort(s, c)
+ }
+
+ private fun ByteBuffer.tryWriteShort(s: Short, c: RingBufferCapacity): Boolean {
+ if (c.tryWriteExact(2)) {
+ if (remaining() < 2) {
+ limit(capacity())
+ putShort(s)
+ carry()
+ } else {
+ putShort(s)
+ }
+
+ bytesWritten(c, 2)
+ return true
+ }
+
+ return false
+ }
+
+ private fun ByteBuffer.tryWriteInt(i: Int, c: RingBufferCapacity): Boolean {
+ if (c.tryWriteExact(4)) {
+ if (remaining() < 4) {
+ limit(capacity())
+ putInt(i)
+ carry()
+ } else {
+ putInt(i)
+ }
+
+ bytesWritten(c, 4)
+ return true
+ }
+
+ return false
+ }
+
+ suspend override fun writeInt(i: Int) {
+ writing {
+ if (!tryWriteInt(i, it)) {
+ writeIntSuspend(i, it)
+ }
+ }
+ }
+
+ private suspend fun ByteBuffer.writeIntSuspend(i: Int, c: RingBufferCapacity) {
+ writeSuspend(4)
+ tryWriteInt(i, c)
+ }
+
+ private fun ByteBuffer.tryWriteLong(l: Long, c: RingBufferCapacity): Boolean {
+ if (c.tryWriteExact(8)) {
+ if (remaining() < 8) {
+ limit(capacity())
+ putLong(l)
+ carry()
+ } else {
+ putLong(l)
+ }
+
+ bytesWritten(c, 8)
+ return true
+ }
+
+ return false
+ }
+
+ suspend override fun writeLong(l: Long) {
+ writing {
+ if (!tryWriteLong(l, it)) {
+ writeLongSuspend(l, it)
+ }
+ }
+ }
+
+ private suspend fun ByteBuffer.writeLongSuspend(l: Long, c: RingBufferCapacity) {
+ writeSuspend(8)
+ tryWriteLong(l, c)
+ }
+
+ suspend override fun writeDouble(d: Double) {
+ writeLong(java.lang.Double.doubleToRawLongBits(d))
+ }
+
+ suspend override fun writeFloat(f: Float) {
+ writeInt(java.lang.Float.floatToRawIntBits(f))
+ }
+
+ suspend override fun writeAvailable(src: ByteBuffer): Int {
+ val copied = writeAsMuchAsPossible(src)
+ if (copied > 0) return copied
+
+ return writeLazySuspend(src)
+ }
+
+ private suspend fun writeLazySuspend(src: ByteBuffer): Int {
+ while (true) {
+ writeSuspend(1)
+ val copied = writeAvailable(src)
+ if (copied > 0) return copied
+ }
+ }
+
+ suspend override fun writeFully(src: ByteBuffer) {
+ writeAsMuchAsPossible(src)
+ if (!src.hasRemaining()) return
+
+ return writeFullySuspend(src)
+ }
+
+ private suspend fun writeFullySuspend(src: ByteBuffer) {
+ while (src.hasRemaining()) {
+ writeSuspend(1)
+ writeAsMuchAsPossible(src)
+ }
+ }
+
+ private fun writeAsMuchAsPossible(src: ByteBuffer): Int {
+ writing {
+ var written = 0
+
+ do {
+ val possibleSize = it.tryWriteAtMost(minOf(src.remaining(), remaining()))
+ if (possibleSize == 0) break
+ require(possibleSize > 0)
+
+ if (remaining() >= src.remaining()) {
+ put(src)
+ } else {
+ repeat(possibleSize) {
+ put(src.get())
+ }
+ }
+
+ written += possibleSize
+
+ prepareBuffer(writeByteOrder, carryIndex(writePosition + written), it.availableForWrite)
+ } while (true)
+
+ bytesWritten(it, written)
+
+ return written
+ }
+
+ return 0
+ }
+
+ private fun writeAsMuchAsPossible(src: ByteArray, offset: Int, length: Int): Int {
+ writing {
+ var written = 0
+
+ do {
+ val possibleSize = it.tryWriteAtMost(minOf(length - written, remaining()))
+ if (possibleSize == 0) break
+ require(possibleSize > 0)
+
+ put(src, offset + written, possibleSize)
+ written += possibleSize
+
+ prepareBuffer(writeByteOrder, carryIndex(writePosition + written), it.availableForWrite)
+ } while (true)
+
+ bytesWritten(it, written)
+
+ return written
+ }
+
+ return 0
+ }
+
+ suspend override fun writeFully(src: ByteArray, offset: Int, length: Int) {
+ var rem = length
+ var off = offset
+
+ while (rem > 0) {
+ val s = writeAsMuchAsPossible(src, off, rem)
+ if (s == 0) break
+
+ off += s
+ rem -= s
+ }
+
+ if (rem == 0) return
+
+ writeFullySuspend(src, off, rem)
+ }
+
+ private tailrec suspend fun writeFullySuspend(src: ByteArray, offset: Int, length: Int) {
+ if (length == 0) return
+ val copied = writeAvailable(src, offset, length)
+ return writeFullySuspend(src, offset + copied, length - copied)
+ }
+
+ suspend override fun writeAvailable(src: ByteArray, offset: Int, length: Int): Int {
+ val size = writeAsMuchAsPossible(src, offset, length)
+ if (size > 0) return size
+ return writeSuspend(src, offset, length)
+ }
+
+ private suspend fun writeSuspend(src: ByteArray, offset: Int, length: Int): Int {
+ while (true) {
+ writeSuspend(1)
+ val size = writeAsMuchAsPossible(src, offset, length)
+ if (size > 0) return size
+ }
+ }
+
+ suspend override fun writePacket(packet: ByteReadPacket) {
+ closed?.sendException?.let { packet.release(); throw it }
+
+ when (packet) {
+ is ByteReadPacketEmpty -> return
+ is ByteReadPacketSingle -> writeSingleBufferPacket(packet)
+ is ByteReadPacketImpl -> writeMultipleBufferPacket(packet)
+ else -> writeExternalPacket(packet)
+ }
+ }
+
+ private suspend fun writeSingleBufferPacket(packet: ByteReadPacketSingle) {
+ val buffer = packet.steal()
+ val t = try {
+ writeAsMuchAsPossible(buffer)
+ null
+ } catch (t: Throwable) {
+ t
+ }
+
+ if (t != null) {
+ packet.pool.recycle(buffer)
+ throw t
+ }
+
+ if (buffer.hasRemaining()) {
+ return writeSingleBufferPacketSuspend(buffer, packet)
+ }
+
+ packet.pool.recycle(buffer)
+ }
+
+ private suspend fun writeMultipleBufferPacket(packet: ByteReadPacketImpl) {
+ var buffer: ByteBuffer? = null
+
+ val t = try {
+ while (packet.remaining > 0) {
+ buffer = packet.steal()
+ writeAsMuchAsPossible(buffer)
+ if (buffer.hasRemaining()) break
+ packet.pool.recycle(buffer)
+ }
+ null
+ } catch (t: Throwable) { t }
+
+ if (t != null) {
+ buffer?.let { packet.pool.recycle(it) }
+ packet.release()
+ throw t
+ }
+
+ if (buffer != null) {
+ return writeMultipleBufferPacketSuspend(buffer, packet)
+ }
+
+ packet.release()
+ }
+
+ private suspend fun writeSingleBufferPacketSuspend(buffer: ByteBuffer, packet: ByteReadPacketSingle) {
+ try {
+ writeFully(buffer)
+ } finally {
+ packet.pool.recycle(buffer)
+ }
+ }
+
+ private suspend fun writeMultipleBufferPacketSuspend(rem: ByteBuffer, packet: ByteReadPacketImpl) {
+ var buffer = rem
+
+ try {
+ do {
+ writeFully(buffer)
+ if (packet.remaining == 0) break
+ packet.pool.recycle(buffer)
+ buffer = packet.steal()
+ } while (true)
+ } finally {
+ packet.pool.recycle(buffer)
+ packet.release()
+ }
+ }
+
+ private suspend fun writeExternalPacket(packet: ByteReadPacket) {
+ val buffer = BufferPool.borrow()
+ val t = try {
+ while (packet.remaining > 0) {
+ buffer.clear()
+ packet.readAvailable(buffer)
+ buffer.flip()
+ writeAsMuchAsPossible(buffer)
+ if (buffer.hasRemaining()) {
+ buffer.compact()
+ break
+ }
+ }
+
+ null
+ } catch (t: Throwable) {
+ t
+ }
+
+ buffer.flip()
+ if (buffer.hasRemaining()) {
+ return writeExternalPacketSuspend(buffer, packet)
+ }
+
+ BufferPool.recycle(buffer)
+ packet.release()
+
+ if (t != null) throw t
+ }
+
+ private suspend fun writeExternalPacketSuspend(buffer: ByteBuffer, packet: ByteReadPacket) {
+ try {
+ do {
+ buffer.compact()
+ packet.readAvailable(buffer)
+ buffer.flip()
+ writeFully(buffer)
+ } while (packet.remaining > 0)
+ } finally {
+ BufferPool.recycle(buffer)
+ packet.release()
+ }
+ }
+
+ /**
+ * Invokes [visitor] for every available batch until all bytes processed or visitor if visitor returns false.
+ * Never invokes [visitor] with empty buffer unless [last] = true. Invokes visitor with last = true at most once
+ * even if there are remaining bytes and visitor returned true.
+ */
+ override suspend fun consumeEachBufferRange(visitor: (buffer: ByteBuffer, last: Boolean) -> Boolean) {
+ if (consumeEachBufferRangeFast(false, visitor)) return
+ consumeEachBufferRangeSuspend(visitor)
+ }
+
+ override fun <R> lookAhead(visitor: LookAheadSession.() -> R): R {
+ if (state === ReadWriteBufferState.Terminated) {
+ return visitor(TerminatedLookAhead)
+ }
+
+ var result: R? = null
+ val rc = reading {
+ result = visitor(this@ByteBufferChannel)
+ true
+ }
+
+ if (!rc) {
+ return visitor(TerminatedLookAhead)
+ }
+
+ return result!!
+ }
+
+ suspend override fun <R> lookAheadSuspend(visitor: suspend LookAheadSuspendSession.() -> R): R {
+ if (state === ReadWriteBufferState.Terminated) {
+ return visitor(TerminatedLookAhead)
+ }
+
+ var result: R? = null
+ val rc = reading {
+ result = visitor(this@ByteBufferChannel)
+ true
+ }
+
+ if (!rc) {
+ if (closed != null || state === ReadWriteBufferState.Terminated) return visitor(TerminatedLookAhead)
+ result = visitor(this)
+ if (!state.idle && state !== ReadWriteBufferState.Terminated) {
+ restoreStateAfterRead()
+ tryTerminate()
+ }
+ }
+
+ return result!!
+ }
+
+ override fun consumed(n: Int) {
+ require(n >= 0)
+
+ state.let { s ->
+ if (!s.capacity.tryReadExact(n)) throw IllegalStateException("Unable to consume $n bytes: not enough available bytes")
+ s.readBuffer.bytesRead(s.capacity, n)
+ }
+ }
+
+ suspend override fun awaitAtLeast(n: Int) {
+ if (readSuspend(n) && state.idle) {
+ setupStateForRead()
+ }
+ }
+
+ override fun request(skip: Int, atLeast: Int): ByteBuffer? {
+ return state.let { s ->
+ val available = s.capacity.availableForRead
+ val rp = readPosition
+
+ if (available < atLeast + skip) return null
+ if (s.idle || (s !is ReadWriteBufferState.Reading && s !is ReadWriteBufferState.ReadingWriting)) return null
+
+ val buffer = s.readBuffer
+
+ val position = buffer.carryIndex(rp + skip)
+ buffer.prepareBuffer(readByteOrder, position, available - skip)
+
+ if (buffer.remaining() >= atLeast) buffer else null
+ }
+ }
+
+ private inline fun consumeEachBufferRangeFast(last: Boolean, visitor: (buffer: ByteBuffer, last: Boolean) -> Boolean): Boolean {
+ if (state === ReadWriteBufferState.Terminated && !last) return false
+
+ val rc = reading {
+ do {
+ val available = state.capacity.availableForRead
+
+ val rem = if (available > 0 || last) {
+ if (!visitor(this, last)) {
+ afterBufferVisited(this, it)
+ return true
+ }
+
+ val consumed = afterBufferVisited(this, it)
+ available - consumed
+ } else 0
+ } while (rem > 0 && !last)
+
+ last
+ }
+
+ if (!rc && closed != null) {
+ visitor(EmptyByteBuffer, true)
+ }
+
+ return rc
+ }
+
+ private suspend fun consumeEachBufferRangeSuspend(visitor: (buffer: ByteBuffer, last: Boolean) -> Boolean): Boolean {
+ var last = false
+
+ do {
+ if (consumeEachBufferRangeFast(last, visitor)) return true
+ if (last) return false
+ if (!readSuspend(1)) {
+ last = true
+ }
+ } while (true)
+ }
+
+ private fun afterBufferVisited(buffer: ByteBuffer, c: RingBufferCapacity): Int {
+ val consumed = buffer.position() - readPosition
+ if (consumed > 0) {
+ if (!c.tryReadExact(consumed)) throw IllegalStateException("Consumed more bytes than available")
+
+ buffer.bytesRead(c, consumed)
+ buffer.prepareBuffer(readByteOrder, readPosition, c.availableForRead)
+ }
+
+ return consumed
+ }
+
+ private suspend fun readUTF8LineToAscii(out: Appendable, limit: Int): Boolean {
+ if (state === ReadWriteBufferState.Terminated) return false
+
+ var cr = false
+ var consumed = 0
+ var unicodeStarted = false
+ var eol = false
+
+ consumeEachBufferRangeFast(false) { buffer, last ->
+ var forceConsume = false
+
+ val rejected = !buffer.decodeASCII { ch ->
+ when {
+ ch == '\r' -> {
+ cr = true
+ true
+ }
+ ch == '\n' -> {
+ eol = true
+ forceConsume = true
+ false
+ }
+ cr -> {
+ cr = false
+ eol = true
+ false
+ }
+ else -> {
+ if (consumed == limit) throw BufferOverflowException()
+ consumed++
+ out.append(ch)
+ true
+ }
+ }
+ }
+
+ if (cr && last) {
+ eol = true
+ }
+
+ if (eol && forceConsume) {
+ buffer.position(buffer.position() + 1)
+ }
+
+ if (rejected && buffer.hasRemaining() && !eol) {
+ unicodeStarted = true
+ false
+ } else
+ !eol && !last
+ }
+
+ if (eol && !unicodeStarted) return true
+ return readUTF8LineToUtf8(out, limit - consumed, cr, consumed)
+ }
+
+ private suspend fun readUTF8LineToUtf8(out: Appendable, limit: Int, cr0: Boolean, consumed0: Int): Boolean {
+ var cr1 = cr0
+ var consumed1 = 0
+ var eol = false
+
+ consumeEachBufferRangeFast(false) { buffer, last ->
+ var forceConsume = false
+
+ val rc = buffer.decodeUTF8 { ch ->
+ when {
+ ch == '\r' -> {
+ cr1 = true
+ true
+ }
+ ch == '\n' -> {
+ eol = true
+ forceConsume = true
+ false
+ }
+ cr1 -> {
+ cr1 = false
+ eol = true
+ false
+ }
+ else -> {
+ if (consumed1 == limit) throw BufferOverflowException()
+ consumed1++
+ out.append(ch)
+ true
+ }
+ }
+ }
+
+ if (cr1 && last) {
+ eol = true
+ }
+
+ if (eol && forceConsume) {
+ buffer.position(buffer.position() + 1)
+ }
+
+ rc != 0 && !eol && !last
+ }
+
+ if (eol) return true
+
+ return readUTF8LineToUtf8Suspend(out, limit, cr1, consumed1 + consumed0)
+ }
+
+ private suspend fun readUTF8LineToUtf8Suspend(out: Appendable, limit: Int, cr0: Boolean, consumed0: Int): Boolean {
+ var cr1 = cr0
+ var consumed1 = 0
+ var eol = false
+ var wrap = 0
+
+ consumeEachBufferRangeSuspend { buffer, last ->
+ var forceConsume = false
+
+ val rc = buffer.decodeUTF8 { ch ->
+ when {
+ ch == '\r' -> {
+ cr1 = true
+ true
+ }
+ ch == '\n' -> {
+ eol = true
+ forceConsume = true
+ false
+ }
+ cr1 -> {
+ cr1 = false
+ eol = true
+ false
+ }
+ else -> {
+ if (consumed1 == limit) throw BufferOverflowException()
+ consumed1++
+ out.append(ch)
+ true
+ }
+ }
+ }
+
+ if (cr1 && last) {
+ eol = true
+ }
+
+ if (eol && forceConsume) {
+ buffer.position(buffer.position() + 1)
+ }
+
+ wrap = maxOf(0, rc)
+
+ wrap == 0 && !eol && !last
+ }
+
+ if (wrap != 0) {
+ if (!readSuspend(wrap)) {
+
+ }
+
+ return readUTF8LineToUtf8Suspend(out, limit, cr1, consumed1)
+ }
+
+ return (consumed1 > 0 || consumed0 > 0 || eol)
+ }
+
+ suspend override fun <A : Appendable> readUTF8LineTo(out: A, limit: Int) = readUTF8LineToAscii(out, limit)
+
+ private fun resumeReadOp() {
+ ReadOp.getAndSet(this, null)?.resume(true)
+ }
+
+ private fun resumeWriteOp() {
+ WriteOp.getAndSet(this, null)?.apply {
+ val closed = closed
+ if (closed == null) resume(Unit) else resumeWithException(closed.sendException)
+ }
+ }
+
+ private fun resumeClosed(cause: Throwable?) {
+ ReadOp.getAndSet(this, null)?.let { c ->
+ if (cause != null)
+ c.resumeWithException(cause)
+ else
+ c.resume(state.capacity.availableForRead > 0)
+ }
+
+ WriteOp.getAndSet(this, null)?.tryResumeWithException(cause ?:
+ ClosedWriteChannelException(DEFAULT_CLOSE_MESSAGE))
+ }
+
+ private tailrec suspend fun readSuspend(size: Int): Boolean {
+ val capacity = state.capacity
+ if (capacity.availableForRead >= size) return true
+
+ closed?.let { c ->
+ if (c.cause != null) throw c.cause
+ return capacity.flush() && capacity.availableForRead >= size
+ }
+
+ if (!readSuspendImpl(size)) return false
+
+ return readSuspend(size)
+ }
+
+ private suspend fun readSuspendImpl(size: Int): Boolean {
+ if (state.capacity.availableForRead >= size) return true
+
+ return suspendCancellableCoroutine(holdCancellability = true) { c ->
+ do {
+ if (state.capacity.availableForRead >= size) {
+ c.resume(true)
+ break
+ }
+
+ closed?.let {
+ if (it.cause != null) {
+ c.resumeWithException(it.cause)
+ } else {
+ c.resume(state.capacity.flush() && state.capacity.availableForRead >= size)
+ }
+ return@suspendCancellableCoroutine
+ }
+ } while (!setContinuation({ readOp }, ReadOp, c, { closed == null && state.capacity.availableForRead < size }))
+ }
+ }
+
+ private suspend fun writeSuspend(size: Int) {
+ closed?.sendException?.let { throw it }
+
+ while (state.capacity.availableForWrite < size && state !== ReadWriteBufferState.IdleEmpty && closed == null) {
+ suspendCancellableCoroutine<Unit>(holdCancellability = true) { c ->
+ do {
+ closed?.sendException?.let { throw it }
+ if (state.capacity.availableForWrite >= size || state === ReadWriteBufferState.IdleEmpty) {
+ c.resume(Unit)
+ break
+ }
+ } while (!setContinuation({ writeOp }, WriteOp, c, { closed == null && state.capacity.availableForWrite < size && state !== ReadWriteBufferState.IdleEmpty }))
+
+ flush()
+ }
+ }
+
+ closed?.sendException?.let { throw it }
+ }
+
+ private inline fun <T, C : CancellableContinuation<T>> setContinuation(getter: () -> C?, updater: AtomicReferenceFieldUpdater<ByteBufferChannel, C?>, continuation: C, predicate: () -> Boolean): Boolean {
+ while (true) {
+ val current = getter()
+ if (current != null) throw IllegalStateException("Operation is already in progress")
+
+ if (!predicate()) {
+ return false
+ }
+
+ if (updater.compareAndSet(this, null, continuation)) {
+ if (predicate() || !updater.compareAndSet(this, continuation, null)) {
+ continuation.initCancellability()
+ return true
+ }
+
+ return false
+ }
+ }
+ }
+
+ private fun newBuffer(): ReadWriteBufferState.Initial {
+ val result = pool.borrow()
+
+ result.readBuffer.order(readByteOrder)
+ result.writeBuffer.order(writeByteOrder)
+ result.capacity.resetForWrite()
+
+ return result
+ }
+
+ private fun releaseBuffer(buffer: ReadWriteBufferState.Initial) {
+ pool.recycle(buffer)
+ }
+
+ // todo: replace with atomicfu
+ private inline fun updateState(block: (ReadWriteBufferState) -> ReadWriteBufferState?):
+ Pair<ReadWriteBufferState, ReadWriteBufferState> = update({ state }, State, block)
+
+ // todo: replace with atomicfu
+ private inline fun <T : Any> update(getter: () -> T, updater: AtomicReferenceFieldUpdater<ByteBufferChannel, T>, block: (old: T) -> T?): Pair<T, T> {
+ while (true) {
+ val old = getter()
+ val newValue = block(old) ?: continue
+ if (old === newValue || updater.compareAndSet(this, old, newValue)) return Pair(old, newValue)
+ }
+ }
+
+ companion object {
+
+ private const val ReservedLongIndex = -8
+
+ // todo: replace with atomicfu, remove companion object
+ private val State = updater(ByteBufferChannel::state)
+ private val WriteOp = updater(ByteBufferChannel::writeOp)
+ private val ReadOp = updater(ByteBufferChannel::readOp)
+ private val Closed = updater(ByteBufferChannel::closed)
+ }
+
+ private object TerminatedLookAhead : LookAheadSuspendSession {
+ override fun consumed(n: Int) {
+ if (n > 0) throw IllegalStateException("Unable to mark $n bytes consumed for already terminated channel")
+ }
+
+ override fun request(skip: Int, atLeast: Int) = null
+
+ suspend override fun awaitAtLeast(n: Int) {
+ }
+ }
+
+ private class ClosedElement(val cause: Throwable?) {
+ val sendException: Throwable
+ get() = cause ?: ClosedWriteChannelException("The channel was closed")
+
+ companion object {
+ val EmptyCause = ClosedElement(null)
+ }
+ }
+}
+
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteChannel.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteChannel.kt
new file mode 100644
index 0000000..ebcfc1b
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteChannel.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.io.internal.EmptyByteBuffer
+
+/**
+ * Channel for asynchronous reading and writing of sequences of bytes.
+ * This is a buffered **single-reader single-writer channel**.
+ *
+ * Read operations can be invoked concurrently with write operations, but multiple reads or multiple writes
+ * cannot be invoked concurrently with themselves. Exceptions are [close] and [flush] which can be invoked
+ * concurrently with any other operations and between themselves at any time.
+ */
+interface ByteChannel : ByteReadChannel, ByteWriteChannel
+
+/**
+ * Creates buffered channel for asynchronous reading and writing of sequences of bytes.
+ */
+public fun ByteChannel(autoFlush: Boolean = false): ByteChannel =
+ ByteBufferChannel(autoFlush)
+
+/**
+ * Creates channel for reading from the specified byte buffer.
+ */
+public fun ByteReadChannel(content: ByteBuffer): ByteReadChannel =
+ ByteBufferChannel(content)
+
+/**
+ * Creates channel for reading from the specified byte array.
+ */
+public fun ByteReadChannel(content: ByteArray): ByteReadChannel =
+ ByteBufferChannel(ByteBuffer.wrap(content))
+
+
+/**
+ * Byte channel that is always empty.
+ */
+val EmptyByteReadChannel: ByteReadChannel = ByteReadChannel(EmptyByteBuffer)
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteChannelCoroutine.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteChannelCoroutine.kt
new file mode 100644
index 0000000..cc12417
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteChannelCoroutine.kt
@@ -0,0 +1,19 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.AbstractCoroutine
+import kotlinx.coroutines.experimental.JobSupport
+import kotlinx.coroutines.experimental.handleCoroutineException
+import kotlin.coroutines.experimental.CoroutineContext
+
+internal open class ByteChannelCoroutine(
+ parentContext: CoroutineContext,
+ open val channel: ByteChannel
+) : AbstractCoroutine<Unit>(parentContext, active = true) {
+ override fun afterCompletion(state: Any?, mode: Int) {
+ val cause = (state as? JobSupport.CompletedExceptionally)?.cause
+ if (!channel.close(cause) && cause != null)
+ handleCoroutineException(context, cause)
+
+ super.afterCompletion(state, mode)
+ }
+}
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteOrder.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteOrder.kt
new file mode 100644
index 0000000..bad461f
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteOrder.kt
@@ -0,0 +1,6 @@
+package kotlinx.coroutines.experimental.io
+
+/**
+ * Byte order.
+ */
+typealias ByteOrder = java.nio.ByteOrder
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteReadChannel.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteReadChannel.kt
new file mode 100644
index 0000000..4d4a2ed
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteReadChannel.kt
@@ -0,0 +1,195 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.io.internal.*
+import kotlinx.coroutines.experimental.io.packet.*
+
+/**
+ * Channel for asynchronous reading of sequences of bytes.
+ * This is a **single-reader channel**.
+ *
+ * Operations on this channel cannot be invoked concurrently.
+ */
+public interface ByteReadChannel {
+ /**
+ * Returns number of bytes that can be read without suspension. Read operations do no suspend and return
+ * immediately when this number is at least the number of bytes requested for read.
+ */
+ public val availableForRead: Int
+
+ /**
+ * Returns `true` if the channel is closed and no remaining bytes are available for read.
+ * It implies that [availableForRead] is zero.
+ */
+ public val isClosedForRead: Boolean
+
+ public val isClosedForWrite: Boolean
+
+ /**
+ * Byte order that is used for multi-byte read operations
+ * (such as [readShort], [readInt], [readLong], [readFloat], and [readDouble]).
+ */
+ public var readByteOrder: ByteOrder
+
+ /**
+ * Number of bytes read from the channel.
+ * It is not guaranteed to be atomic so could be updated in the middle of long running read operation.
+ */
+ public val totalBytesRead: Long
+
+ /**
+ * Reads all available bytes to [dst] buffer and returns immediately or suspends if no bytes available
+ * @return number of bytes were read or `-1` if the channel has been closed
+ */
+ suspend fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int
+ suspend fun readAvailable(dst: ByteArray) = readAvailable(dst, 0, dst.size)
+ suspend fun readAvailable(dst: ByteBuffer): Int
+
+ /**
+ * Reads all [length] bytes to [dst] buffer or fails if channel has been closed.
+ * Suspends if not enough bytes available.
+ */
+ suspend fun readFully(dst: ByteArray, offset: Int, length: Int)
+ suspend fun readFully(dst: ByteArray) = readFully(dst, 0, dst.size)
+ suspend fun readFully(dst: ByteBuffer): Int
+
+ /**
+ * Reads the specified amount of bytes and makes a byte packet from them. Fails if channel has been closed
+ * and not enough bytes available.
+ */
+ suspend fun readPacket(size: Int): ByteReadPacket
+
+ /**
+ * Reads a long number (suspending if not enough bytes available) or fails if channel has been closed
+ * and not enough bytes.
+ */
+ suspend fun readLong(): Long
+
+ /**
+ * Reads an int number (suspending if not enough bytes available) or fails if channel has been closed
+ * and not enough bytes.
+ */
+ suspend fun readInt(): Int
+
+ /**
+ * Reads a short number (suspending if not enough bytes available) or fails if channel has been closed
+ * and not enough bytes.
+ */
+ suspend fun readShort(): Short
+
+ /**
+ * Reads a byte (suspending if no bytes available yet) or fails if channel has been closed
+ * and not enough bytes.
+ */
+ suspend fun readByte(): Byte
+
+ /**
+ * Reads a boolean value (suspending if no bytes available yet) or fails if channel has been closed
+ * and not enough bytes.
+ */
+ suspend fun readBoolean(): Boolean
+
+ /**
+ * Reads double number (suspending if not enough bytes available) or fails if channel has been closed
+ * and not enough bytes.
+ */
+ suspend fun readDouble(): Double
+
+ /**
+ * Reads float number (suspending if not enough bytes available) or fails if channel has been closed
+ * and not enough bytes.
+ */
+ suspend fun readFloat(): Float
+
+ /**
+ * For every available bytes range invokes [visitor] function until it return false or end of stream encountered
+ */
+ suspend fun consumeEachBufferRange(visitor: (buffer: ByteBuffer, last: Boolean) -> Boolean)
+
+ fun <R> lookAhead(visitor: LookAheadSession.() -> R): R
+ suspend fun <R> lookAheadSuspend(visitor: suspend LookAheadSuspendSession.() -> R): R
+
+ /**
+ * Reads a line of UTF-8 characters to the specified [out] buffer up to [limit] characters.
+ * Supports both CR-LF and LF line endings.
+ * Throws an exception if the specified [limit] has been exceeded.
+ *
+ * @return `true` if line has been read (possibly empty) or `false` if channel has been closed
+ * and no characters were read.
+ */
+ suspend fun <A : Appendable> readUTF8LineTo(out: A, limit: Int = Int.MAX_VALUE): Boolean
+}
+
+/**
+ * Reads up to [limit] bytes from receiver channel and writes them to [dst] channel.
+ * Closes [dst] channel if fails to read or write with cause exception.
+ * @return a number of copied bytes
+ */
+suspend fun ByteReadChannel.copyTo(dst: ByteWriteChannel, limit: Long = Long.MAX_VALUE): Long {
+ val buffer = BufferPool.borrow()
+ try {
+ var copied = 0L
+
+ while (copied < limit) {
+ buffer.clear()
+ if (limit - copied < buffer.limit()) {
+ buffer.limit((limit - copied).toInt())
+ }
+ val size = readAvailable(buffer)
+ if (size == -1) break
+
+ buffer.flip()
+ dst.writeFully(buffer)
+ copied += size
+ }
+ return copied
+ } catch (t: Throwable) {
+ dst.close(t)
+ throw t
+ } finally {
+ BufferPool.recycle(buffer)
+ }
+}
+
+/**
+ * Reads all the bytes from receiver channel and writes them to [dst] channel and then closes it.
+ * Closes [dst] channel if fails to read or write with cause exception.
+ * @return a number of copied bytes
+ */
+suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel): Long {
+ val count = copyTo(dst)
+ dst.close()
+ return count
+}
+
+/**
+ * Reads all the bytes from receiver channel and builds a packet that is returned unless the specified [limit] exceeded.
+ * It will simply stop reading and return packet of size [limit] in this case
+ */
+suspend fun ByteReadChannel.readRemaining(limit: Int = Int.MAX_VALUE): ByteReadPacket {
+ val buffer = BufferPool.borrow()
+ val packet = WritePacket()
+
+ try {
+ var copied = 0L
+
+ while (copied < limit) {
+ buffer.clear()
+ if (limit - copied < buffer.limit()) {
+ buffer.limit((limit - copied).toInt())
+ }
+ val size = readAvailable(buffer)
+ if (size == -1) break
+
+ buffer.flip()
+ packet.writeFully(buffer)
+ copied += size
+ }
+
+ return packet.build()
+ } catch (t: Throwable) {
+ packet.release()
+ throw t
+ } finally {
+ BufferPool.recycle(buffer)
+ }
+}
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteWriteChannel.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteWriteChannel.kt
new file mode 100644
index 0000000..58a9510
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteWriteChannel.kt
@@ -0,0 +1,184 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.io.packet.ByteReadPacket
+import kotlinx.coroutines.experimental.io.packet.ByteWritePacket
+import kotlinx.coroutines.experimental.io.packet.buildPacket
+import java.nio.ByteBuffer
+import java.nio.CharBuffer
+import java.util.concurrent.CancellationException
+
+/**
+ * Channel for asynchronous writing of sequences of bytes.
+ * This is a **single-writer channel**.
+ *
+ * Operations on this channel cannot be invoked concurrently, unless explicitly specified otherwise
+ * in description. Exceptions are [close] and [flush].
+ */
+public interface ByteWriteChannel {
+ /**
+ * Returns number of bytes that can be written without suspension. Write operations do no suspend and return
+ * immediately when this number is at least the number of bytes requested for write.
+ */
+ public val availableForWrite: Int
+
+ /**
+ * Returns `true` is channel has been closed and attempting to write to the channel will cause an exception.
+ */
+ public val isClosedForWrite: Boolean
+
+ /**
+ * Returns `true` if channel flushes automatically all pending bytes after every write function call.
+ * If `false` then flush only happens at manual [flush] invocation or when the buffer is full.
+ */
+ public val autoFlush: Boolean
+
+ /**
+ * Byte order that is used for multi-byte write operations
+ * (such as [writeShort], [writeInt], [writeLong], [writeFloat], and [writeDouble]).
+ */
+ public var writeByteOrder: ByteOrder
+
+ /**
+ * Number of bytes written to the channel.
+ * It is not guaranteed to be atomic so could be updated in the middle of write operation.
+ */
+ public val totalBytesWritten: Long
+
+ /**
+ * Writes as much as possible and only suspends if buffer is full
+ */
+ suspend fun writeAvailable(src: ByteArray, offset: Int, length: Int): Int
+ suspend fun writeAvailable(src: ByteArray) = writeAvailable(src, 0, src.size)
+ suspend fun writeAvailable(src: ByteBuffer): Int
+
+ /**
+ * Writes all [src] bytes and suspends until all bytes written. Causes flush if buffer filled up or when [autoFlush]
+ * Crashes if channel get closed while writing.
+ */
+ suspend fun writeFully(src: ByteArray, offset: Int, length: Int)
+ suspend fun writeFully(src: ByteArray) = writeFully(src, 0, src.size)
+ suspend fun writeFully(src: ByteBuffer)
+
+ /**
+ * Writes a [packet] fully or fails if channel get closed before the whole packet has been written
+ */
+ suspend fun writePacket(packet: ByteReadPacket)
+ /**
+ * Writes long number and suspends until written.
+ * Crashes if channel get closed while writing.
+ */
+ suspend fun writeLong(l: Long)
+
+ /**
+ * Writes int number and suspends until written.
+ * Crashes if channel get closed while writing.
+ */
+ suspend fun writeInt(i: Int)
+
+ /**
+ * Writes short number and suspends until written.
+ * Crashes if channel get closed while writing.
+ */
+ suspend fun writeShort(s: Short)
+
+ /**
+ * Writes byte and suspends until written.
+ * Crashes if channel get closed while writing.
+ */
+ suspend fun writeByte(b: Byte)
+
+ /**
+ * Writes double number and suspends until written.
+ * Crashes if channel get closed while writing.
+ */
+ suspend fun writeDouble(d: Double)
+
+ /**
+ * Writes float number and suspends until written.
+ * Crashes if channel get closed while writing.
+ */
+ suspend fun writeFloat(f: Float)
+
+ /**
+ * Closes this channel with an optional exceptional [cause].
+ * It flushes all pending write bytes (via [flush]).
+ * This is an idempotent operation -- repeated invocations of this function have no effect and return `false`.
+ *
+ * A channel that was closed without a [cause], is considered to be _closed normally_.
+ * A channel that was closed with non-null [cause] is called a _failed channel_. Attempts to read or
+ * write on a failed channel throw this cause exception.
+ *
+ * After invocation of this operation [isClosedForWrite] starts returning `true` and
+ * all subsequent write operations throw [ClosedWriteChannelException] or the specified [cause].
+ * However, [isClosedForRead][ByteReadChannel.isClosedForRead] on the side of [ByteReadChannel]
+ * starts returning `true` only after all written bytes have been read.
+ */
+ public fun close(cause: Throwable? = null): Boolean
+
+ /**
+ * Flushes all pending write bytes making them available for read.
+ *
+ * This function is thread-safe and can be invoked in any thread at any time.
+ * It does nothing when invoked on a closed channel.
+ */
+ public fun flush()
+}
+
+suspend fun ByteWriteChannel.writeShort(s: Int) {
+ writeShort((s and 0xffff).toShort())
+}
+
+suspend fun ByteWriteChannel.writeByte(b: Int) {
+ writeByte((b and 0xff).toByte())
+}
+
+suspend fun ByteWriteChannel.writeInt(i: Long) {
+ writeInt(i.toInt())
+}
+
+suspend fun ByteWriteChannel.writeStringUtf8(s: CharSequence) {
+ val packet = buildPacket {
+ writeStringUtf8(s)
+ }
+ writePacket(packet)
+}
+
+suspend fun ByteWriteChannel.writeStringUtf8(s: CharBuffer) {
+ val packet = buildPacket {
+ writeStringUtf8(s)
+ }
+ writePacket(packet)
+}
+
+suspend fun ByteWriteChannel.writeStringUtf8(s: String) {
+ val packet = buildPacket {
+ writeStringUtf8(s)
+ }
+ writePacket(packet)
+}
+
+suspend fun ByteWriteChannel.writeBoolean(b: Boolean) {
+ writeByte(if (b) 1 else 0)
+}
+
+/**
+ * Writes UTF16 character
+ */
+suspend fun ByteWriteChannel.writeChar(ch: Char) {
+ writeShort(ch.toInt())
+}
+
+inline suspend fun ByteWriteChannel.writePacket(builder: ByteWritePacket.() -> Unit) {
+ writePacket(buildPacket(builder))
+}
+
+suspend fun ByteWriteChannel.writePacketSuspend(builder: suspend ByteWritePacket.() -> Unit) {
+ writePacket(buildPacket { builder() })
+}
+
+/**
+ * Indicates attempt to write on [isClosedForWrite][ByteWriteChannel.isClosedForWrite] channel
+ * that was closed without a cause. A _failed_ channel rethrows the original [close][ByteWriteChannel.close] cause
+ * exception on send attempts.
+ */
+public class ClosedWriteChannelException(message: String?) : CancellationException(message)
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/Delimited.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/Delimited.kt
new file mode 100644
index 0000000..ad1a8f8
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/Delimited.kt
@@ -0,0 +1,169 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.io.internal.*
+import java.io.*
+
+/**
+ * Reads from the channel to the specified [dst] byte buffer until one of the following:
+ * - channel's end
+ * - [dst] capacity exceeded
+ * - [delimiter] bytes encountered
+ *
+ * If [delimiter] bytes encountered then these bytes remain unconsumed
+ *
+ * @return non-negative number of copied bytes, possibly 0
+ */
+suspend fun ByteReadChannel.readUntilDelimiter(delimiter: ByteBuffer, dst: ByteBuffer): Int {
+ require(delimiter.hasRemaining())
+ require(delimiter !== dst)
+
+ var copied = 0
+ var endFound = false
+
+ lookAhead {
+ do {
+ val rc = tryCopyUntilDelimiter(delimiter, dst)
+ if (rc == 0) break
+ val size = if (rc < 0) {
+ endFound = true
+ -rc
+ } else rc
+ copied += size
+ } while (dst.hasRemaining() && !endFound)
+ }
+
+ return when {
+ copied == 0 && isClosedForRead -> -1
+ !dst.hasRemaining() || endFound -> copied
+ else -> readUntilDelimiterSuspend(delimiter, dst, copied)
+ }
+}
+
+suspend fun ByteReadChannel.skipDelimiter(delimiter: ByteBuffer) {
+ require(delimiter.hasRemaining())
+
+ var found = false
+
+ lookAhead {
+ found = tryEnsureDelimiter(delimiter) == delimiter.remaining()
+ }
+
+ if (!found) {
+ skipDelimiterSuspend(delimiter)
+ }
+}
+
+private suspend fun ByteReadChannel.skipDelimiterSuspend(delimiter: ByteBuffer) {
+ lookAheadSuspend {
+ awaitAtLeast(delimiter.remaining())
+ if (tryEnsureDelimiter(delimiter) != delimiter.remaining()) throw IOException("Broken delimiter occurred")
+ }
+}
+
+private suspend fun ByteReadChannel.readUntilDelimiterSuspend(delimiter: ByteBuffer, dst: ByteBuffer, copied0: Int): Int {
+ require(delimiter !== dst)
+ require(copied0 >= 0)
+
+ var endFound = false
+ val copied = lookAheadSuspend {
+ var copied = copied0
+
+ do {
+ awaitAtLeast(1)
+ val rc = tryCopyUntilDelimiter(delimiter, dst)
+ if (rc == 0) {
+ if (startsWithDelimiter(delimiter) == delimiter.remaining()) {
+ endFound = true
+ break
+ }
+ if (isClosedForWrite) {
+ break
+ } else {
+ awaitAtLeast(delimiter.remaining())
+ continue
+ }
+ }
+
+ val size = if (rc <= 0) {
+ endFound = true
+ -rc
+ } else rc
+ copied += size
+ } while (dst.hasRemaining() && !endFound)
+
+ copied
+ }
+
+ return when {
+ copied > 0 && isClosedForWrite && !endFound -> copied + readAvailable(dst).coerceAtLeast(0)
+ copied == 0 && isClosedForRead -> -1
+ else -> copied
+ }
+}
+
+/**
+ * @return a positive number of bytes copied if no [delimiter] found yet or a negated number of bytes copied if
+ * the delimited has been found, or 0 if no buffer available (not yet ready or EOF)
+ */
+private fun LookAheadSession.tryCopyUntilDelimiter(delimiter: ByteBuffer, dst: ByteBuffer): Int {
+ var endFound = false
+ val buffer = request(0, 1) ?: return 0
+ val index = buffer.indexOfPartial(delimiter)
+ val size = if (index != -1) {
+ val found = minOf(buffer.remaining() - index, delimiter.remaining())
+ val notKnown = delimiter.remaining() - found
+
+ if (notKnown == 0) {
+ endFound = true
+ dst.putLimited(buffer, buffer.position() + index)
+ } else {
+ val remembered = buffer.duplicate()
+ val next = request(index + found, 1)
+ if (next == null) {
+ dst.putLimited(remembered, remembered.position() + index)
+ } else if (next.startsWith(delimiter, found)) {
+ if (next.remaining() >= notKnown) {
+ endFound = true
+ dst.putLimited(remembered, remembered.position() + index)
+ } else {
+ dst.putLimited(remembered, remembered.position() + index)
+ }
+ } else {
+ dst.putLimited(remembered, remembered.position() + index + 1)
+ }
+ }
+ } else {
+ dst.putAtMost(buffer)
+ }
+ consumed(size)
+
+ return if (endFound) -size else size
+}
+
+private fun LookAheadSession.tryEnsureDelimiter(delimiter: ByteBuffer): Int {
+ val found = startsWithDelimiter(delimiter)
+ if (found == -1) throw IOException("Failed to skip delimiter: actual bytes differ from delimiter bytes")
+ if (found < delimiter.remaining()) return found
+
+ consumed(delimiter.remaining())
+ return delimiter.remaining()
+}
+
+/**
+ * @return Number of bytes of the delimiter found (possibly 0 if no bytes available yet) or -1 if it doesn't start
+ */
+private fun LookAheadSession.startsWithDelimiter(delimiter: ByteBuffer): Int {
+ val buffer = request(0, 1) ?: return 0
+ val index = buffer.indexOfPartial(delimiter)
+ if (index != 0) return -1
+
+ val found = minOf(buffer.remaining() - index, delimiter.remaining())
+ val notKnown = delimiter.remaining() - found
+
+ if (notKnown > 0) {
+ val next = request(index + found, notKnown) ?: return found
+ if (!next.startsWith(delimiter, found)) return -1
+ }
+
+ return delimiter.remaining()
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/LookAheadSession.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/LookAheadSession.kt
new file mode 100644
index 0000000..045c096
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/LookAheadSession.kt
@@ -0,0 +1,41 @@
+package kotlinx.coroutines.experimental.io
+
+interface LookAheadSession {
+ /**
+ * Marks [n] bytes as consumed so the corresponding range becomes available for writing
+ */
+ fun consumed(n: Int)
+
+ /**
+ * Request byte buffer range skipping [skip] bytes and [atLeast] bytes length
+ * @return byte buffer for the requested range or null if it is impossible to provide such a buffer
+ *
+ * There are the following reasons for this function to return `null`:
+ * - not enough bytes available yet (should be at least `skip + atLeast` bytes available)
+ * - due to buffer fragmentation is is impossible to represent the requested range as a single byte buffer
+ * - end of stream encountered and all bytes were consumed
+ * - channel has been closed with an exception so buffer has been recycled
+ */
+ fun request(skip: Int, atLeast: Int): ByteBuffer?
+}
+
+interface LookAheadSuspendSession : LookAheadSession {
+ /**
+ * Suspend until [n] bytes become available or end of stream encountered (possibly due to exceptional close)
+ */
+ suspend fun awaitAtLeast(n: Int)
+}
+
+inline fun LookAheadSession.consumeEachRemaining(visitor: (ByteBuffer) -> Boolean) {
+ do {
+ val cont = request(0, 1)?.let {
+ val s = it.remaining()
+ val rc = visitor(it)
+ consumed(s)
+ rc
+ } ?: false
+
+ if (!cont) break
+ } while (true)
+}
+
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/MigrationUtils.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/MigrationUtils.kt
new file mode 100644
index 0000000..e4815c2
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/MigrationUtils.kt
@@ -0,0 +1,39 @@
+package kotlinx.coroutines.experimental.io
+
+/**
+ * See [java.io.DataOutput.writeUTF]
+ */
+@Deprecated("Use writeStringUtf8 instead", ReplaceWith("writeStringUtf8(s)"))
+suspend fun ByteWriteChannel.writeUTF(s: String) {
+ writeStringUtf8(s)
+}
+
+/**
+ * See [java.io.DataOutput.writeChars]
+ */
+suspend fun ByteWriteChannel.writeChars(s: String) {
+ for (ch in s) {
+ writeShort(ch.toInt())
+ }
+}
+
+/**
+ * See [java.io.DataOutput.writeBytes]
+ */
+suspend fun ByteWriteChannel.writeBytes(s: String) {
+ for (ch in s) {
+ writeByte(ch.toInt())
+ }
+}
+
+/**
+ * See [java.io.DataOutput.write]
+ */
+@Deprecated("Use writeFully instead", ReplaceWith("writeFully(src)"))
+suspend fun ByteWriteChannel.write(src: ByteArray) = writeFully(src)
+
+/**
+ * See [java.io.DataOutput.write]
+ */
+@Deprecated("Use writeFully instead", ReplaceWith("writeFully(src, offset, length)"))
+suspend fun ByteWriteChannel.write(src: ByteArray, offset: Int, length: Int) = writeFully(src, offset, length)
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ReaderJob.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ReaderJob.kt
new file mode 100644
index 0000000..189004d
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ReaderJob.kt
@@ -0,0 +1,37 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.CoroutineScope
+import kotlinx.coroutines.experimental.Job
+import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlin.coroutines.experimental.CoroutineContext
+import kotlin.coroutines.experimental.startCoroutine
+
+/**
+ * A coroutine job that is reading from a byte channel
+ */
+interface ReaderJob : Job {
+ /**
+ * A reference to the channel that this coroutine is reading from
+ */
+ val channel: ByteWriteChannel
+}
+
+interface ReaderScope : CoroutineScope {
+ val channel: ByteReadChannel
+}
+
+fun reader(coroutineContext: CoroutineContext,
+ channel: ByteChannel,
+ block: suspend ReaderScope.() -> Unit): ReaderJob {
+ val coroutine = ReaderCoroutine(newCoroutineContext(coroutineContext), channel)
+ coroutine.initParentJob(coroutineContext[Job])
+ block.startCoroutine(coroutine, coroutine)
+ return coroutine
+}
+
+fun reader(coroutineContext: CoroutineContext,
+ autoFlush: Boolean = false,
+ block: suspend ReaderScope.() -> Unit): ReaderJob = reader(coroutineContext, ByteChannel(autoFlush), block)
+
+private class ReaderCoroutine(context: CoroutineContext, channel: ByteChannel)
+ : ByteChannelCoroutine(context, channel), ReaderJob, ReaderScope
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/Strings.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/Strings.kt
new file mode 100644
index 0000000..c6923cb
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/Strings.kt
@@ -0,0 +1,14 @@
+package kotlinx.coroutines.experimental.io
+
+suspend fun ByteReadChannel.readASCIILine(estimate: Int = 16, limit: Int = Int.MAX_VALUE): String? {
+ val sb = StringBuilder(estimate)
+ return if (readUTF8LineTo(sb, limit)) sb.toString() else null
+}
+
+suspend fun ByteReadChannel.readUTF8Line(estimate: Int = 16, limit: Int = Int.MAX_VALUE): String? {
+ val sb = StringBuilder(estimate)
+ return if (readUTF8LineTo(sb, limit)) sb.toString() else null
+}
+
+@Deprecated("Use readUTF8Line or readASCIILine instead", ReplaceWith("readUTF8Line(estimate, limit)"))
+suspend fun ByteReadChannel.readLine(estimate: Int = 16, limit: Int = Int.MAX_VALUE): String? = readUTF8Line(estimate, limit)
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/WriterJob.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/WriterJob.kt
new file mode 100644
index 0000000..2031ae3
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/WriterJob.kt
@@ -0,0 +1,38 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.CoroutineScope
+import kotlinx.coroutines.experimental.Job
+import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlin.coroutines.experimental.CoroutineContext
+import kotlin.coroutines.experimental.startCoroutine
+
+/**
+ * A coroutine job that is writing to a byte channel
+ */
+interface WriterJob : Job {
+ /**
+ * A reference to the channel that this coroutine is writing to
+ */
+ val channel: ByteReadChannel
+}
+
+interface WriterScope : CoroutineScope {
+ val channel: ByteWriteChannel
+}
+
+fun writer(coroutineContext: CoroutineContext,
+ channel: ByteChannel,
+ block: suspend WriterScope.() -> Unit): WriterJob {
+ val coroutine = WriterCoroutine(newCoroutineContext(coroutineContext), channel)
+ coroutine.initParentJob(coroutineContext[Job])
+ block.startCoroutine(coroutine, coroutine)
+ return coroutine
+}
+
+fun writer(coroutineContext: CoroutineContext,
+ autoFlush: Boolean = false,
+ block: suspend WriterScope.() -> Unit): WriterJob = writer(coroutineContext, ByteChannel(autoFlush), block)
+
+private class WriterCoroutine(ctx: CoroutineContext, channel: ByteChannel)
+ : ByteChannelCoroutine(ctx, channel), WriterScope, WriterJob
+
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/ObjectPool.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/ObjectPool.kt
new file mode 100644
index 0000000..822a1c8
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/ObjectPool.kt
@@ -0,0 +1,144 @@
+package kotlinx.coroutines.experimental.io.internal
+
+import java.nio.ByteBuffer
+import java.util.concurrent.atomic.AtomicReferenceArray
+
+internal interface ObjectPool<T : Any> {
+ val capacity: Int
+ fun borrow(): T
+ fun recycle(instance: T) // can only recycle what was borrowed before
+ fun dispose()
+}
+
+internal val BUFFER_SIZE = getIOIntProperty("BufferSize", 4096)
+private val BUFFER_POOL_SIZE = getIOIntProperty("BufferPoolSize", 2048)
+private val BUFFER_OBJECT_POOL_SIZE = getIOIntProperty("BufferObjectPoolSize", 1024)
+
+// ------------- standard shared pool objects -------------
+
+internal val BufferPool: ObjectPool<ByteBuffer> =
+ object : ObjectPoolImpl<ByteBuffer>(BUFFER_POOL_SIZE) {
+ override fun produceInstance(): ByteBuffer =
+ ByteBuffer.allocateDirect(BUFFER_SIZE)
+ override fun clearInstance(instance: ByteBuffer): ByteBuffer =
+ instance.also { it.clear() }
+ override fun validateInstance(instance: ByteBuffer) {
+ require(instance.capacity() == BUFFER_SIZE)
+ }
+ }
+
+internal val BufferObjectPool: ObjectPool<ReadWriteBufferState.Initial> =
+ object: ObjectPoolImpl<ReadWriteBufferState.Initial>(BUFFER_OBJECT_POOL_SIZE) {
+ override fun produceInstance() =
+ ReadWriteBufferState.Initial(BufferPool.borrow())
+ override fun disposeInstance(instance: ReadWriteBufferState.Initial) {
+ BufferPool.recycle(instance.backingBuffer)
+ }
+ }
+
+internal val BufferObjectNoPool: ObjectPool<ReadWriteBufferState.Initial> =
+ object : NoPoolImpl<ReadWriteBufferState.Initial>() {
+ override fun borrow(): ReadWriteBufferState.Initial =
+ ReadWriteBufferState.Initial(ByteBuffer.allocateDirect(BUFFER_SIZE))
+ }
+
+// ------------- ObjectPoolImpl -------------
+
+private const val MULTIPLIER = 4
+private const val PROBE_COUNT = 8 // number of attepts to find a slot
+private const val MAGIC = 2654435769.toInt() // fractional part of golden ratio
+private const val MAX_CAPACITY = Int.MAX_VALUE / MULTIPLIER
+
+internal abstract class ObjectPoolImpl<T : Any>(final override val capacity: Int) : ObjectPool<T> {
+ init {
+ require(capacity > 0) { "capacity should be positive but it is $capacity" }
+ require(capacity <= MAX_CAPACITY) { "capacity should be less or equal to $MAX_CAPACITY but it is $capacity"}
+ }
+
+ protected abstract fun produceInstance(): T // factory
+ protected open fun clearInstance(instance: T) = instance // optional cleaning of poped items
+ protected open fun validateInstance(instance: T) {} // optional validation for recycled items
+ protected open fun disposeInstance(instance: T) {} // optional destruction of unpoolable items
+
+ @Volatile
+ private var top: Long = 0L
+
+ // closest power of 2 that is equal or larger than capacity * MULTIPLIER
+ private val maxIndex = Integer.highestOneBit(capacity * MULTIPLIER - 1) * 2
+ private val shift = Integer.numberOfLeadingZeros(maxIndex) + 1 // for hash function
+
+ // zero index is reserved for both
+ private val instances = AtomicReferenceArray<T?>(maxIndex + 1)
+ private val next = IntArray(maxIndex + 1)
+
+ override fun borrow(): T =
+ tryPop()?.let { clearInstance(it) } ?: produceInstance()
+
+ override fun recycle(instance: T) {
+ validateInstance(instance)
+ if (!tryPush(instance)) disposeInstance(instance)
+ }
+
+ override fun dispose() {
+ while (true) {
+ val instance = tryPop() ?: return
+ disposeInstance(instance)
+ }
+ }
+
+ private fun tryPush(instance: T): Boolean {
+ var index = ((System.identityHashCode(instance) * MAGIC) ushr shift) + 1
+ repeat (PROBE_COUNT) {
+ if (instances.compareAndSet(index, null, instance)) {
+ pushTop(index)
+ return true
+ }
+ if (--index == 0) index = maxIndex
+ }
+ return false
+ }
+
+ private fun tryPop(): T? {
+ val index = popTop()
+ return if (index == 0) null else instances.getAndSet(index, null)
+ }
+
+ private fun pushTop(index: Int) {
+ require(index > 0)
+ while (true) { // lock-free loop on top
+ val top = this.top // volatile read
+ val topVersion = (top shr 32 and 0xffffffffL) + 1L
+ val topIndex = (top and 0xffffffffL).toInt()
+ val newTop = topVersion shl 32 or index.toLong()
+ next[index] = topIndex
+ if (Top.compareAndSet(this, top, newTop)) return
+ }
+ }
+
+ private fun popTop(): Int {
+ while (true) { // lock-free loop on top
+ val top = this.top // volatile read
+ if (top == 0L) return 0
+ val newVersion = (top shr 32 and 0xffffffffL) + 1L
+ val topIndex = (top and 0xffffffffL).toInt()
+ if (topIndex == 0) return 0
+ val next = next[topIndex]
+ val newTop = newVersion shl 32 or next.toLong()
+ if (Top.compareAndSet(this, top, newTop)) return topIndex
+ }
+ }
+
+ companion object {
+ // todo: replace with atomicfu, remove companion object
+ private val Top = longUpdater(ObjectPoolImpl<*>::top)
+ }
+}
+
+// ------------- NoPoolImpl -------------
+
+internal abstract class NoPoolImpl<T : Any> : ObjectPool<T> {
+ override val capacity: Int get() = 0
+ override abstract fun borrow(): T
+ override fun recycle(instance: T) {}
+ override fun dispose() {}
+}
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/ReadWriteBufferState.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/ReadWriteBufferState.kt
new file mode 100644
index 0000000..37ab97a
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/ReadWriteBufferState.kt
@@ -0,0 +1,91 @@
+package kotlinx.coroutines.experimental.io.internal
+
+import java.nio.ByteBuffer
+
+// this is MAGICAL constant that is tied to the code ByteBufferChannel (that is how much it needs extra)
+internal const val RESERVED_SIZE = 8
+
+internal val EmptyByteBuffer: ByteBuffer = ByteBuffer.allocate(0)
+internal val EmptyCapacity = RingBufferCapacity(0)
+
+internal sealed class ReadWriteBufferState(
+ @JvmField val backingBuffer: ByteBuffer,
+ @JvmField val capacity: RingBufferCapacity
+) {
+ open val idle: Boolean get() = false
+ open val readBuffer: ByteBuffer get() = error("read buffer is not available in state $this")
+ open val writeBuffer: ByteBuffer get() = error("write buffer is not available in state $this")
+
+ internal open fun startReading(): ReadWriteBufferState = error("Reading is not available in state $this")
+ internal open fun startWriting(): ReadWriteBufferState = error("Writing is not available in state $this")
+ internal open fun stopReading(): ReadWriteBufferState = error("Unable to stop reading in state $this")
+ internal open fun stopWriting(): ReadWriteBufferState = error("Unable to stop writing in state $this")
+
+ object IdleEmpty : ReadWriteBufferState(EmptyByteBuffer, EmptyCapacity) {
+ override val idle: Boolean get() = true
+ override fun toString() = "IDLE(empty)"
+ }
+
+ class Initial(
+ backingBuffer: ByteBuffer,
+ reservedSize: Int = RESERVED_SIZE
+ ) : ReadWriteBufferState(backingBuffer, RingBufferCapacity(backingBuffer.capacity() - reservedSize)) {
+ init {
+ require(backingBuffer.position() == 0)
+ require(backingBuffer.limit() == backingBuffer.capacity())
+ }
+ override val writeBuffer: ByteBuffer = backingBuffer.duplicate() // defensive copy of buffer's state
+ override val readBuffer: ByteBuffer = backingBuffer.duplicate() // must have a separate buffer state here
+ // all other possible states
+ internal val idleState = IdleNonEmpty(this)
+ internal val readingState = Reading(this)
+ internal val writingState = Writing(this)
+ internal val readingWritingState = ReadingWriting(this)
+ // state transitions
+ override fun startReading() = readingState
+ override fun startWriting() = writingState
+ override val idle: Boolean get() = error("Not available for initial state")
+ override fun toString() = "Initial"
+ }
+
+ class IdleNonEmpty internal constructor(
+ val initial: Initial // public here, so can release initial state when idle
+ ) : ReadWriteBufferState(initial.backingBuffer, initial.capacity) {
+ override fun startReading() = initial.readingState
+ override fun startWriting() = initial.writingState
+ override val idle: Boolean get() = true
+ override fun toString() = "IDLE(with buffer)"
+ }
+
+ class Reading internal constructor(
+ private val initial: Initial
+ ) : ReadWriteBufferState(initial.backingBuffer, initial.capacity) {
+ override val readBuffer: ByteBuffer get() = initial.readBuffer
+ override fun startWriting() = initial.readingWritingState
+ override fun stopReading() = initial.idleState
+ override fun toString() = "Reading"
+ }
+
+ class Writing internal constructor(
+ private val initial: Initial
+ ) : ReadWriteBufferState(initial.backingBuffer, initial.capacity) {
+ override val writeBuffer: ByteBuffer get() = initial.writeBuffer
+ override fun startReading() = initial.readingWritingState
+ override fun stopWriting() = initial.idleState
+ override fun toString() = "Writing"
+ }
+
+ class ReadingWriting internal constructor(
+ private val initial: Initial
+ ) : ReadWriteBufferState(initial.backingBuffer, initial.capacity) {
+ override val readBuffer: ByteBuffer get() = initial.readBuffer
+ override val writeBuffer: ByteBuffer get() = initial.writeBuffer
+ override fun stopReading() = initial.writingState
+ override fun stopWriting() = initial.readingState
+ override fun toString() = "Reading+Writing"
+ }
+
+ object Terminated : ReadWriteBufferState(EmptyByteBuffer, EmptyCapacity) {
+ override fun toString() = "Terminated"
+ }
+}
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/RingBufferCapacity.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/RingBufferCapacity.kt
new file mode 100644
index 0000000..529b817
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/RingBufferCapacity.kt
@@ -0,0 +1,109 @@
+package kotlinx.coroutines.experimental.io.internal
+
+internal class RingBufferCapacity(private val totalCapacity: Int) {
+ @Volatile @JvmField
+ var availableForRead = 0
+
+ @Volatile @JvmField
+ var availableForWrite = totalCapacity
+
+ @Volatile @JvmField
+ var pendingToFlush = 0
+
+ // concurrent unsafe!
+ fun resetForWrite() {
+ availableForRead = 0
+ availableForWrite = totalCapacity
+ pendingToFlush = 0
+ }
+
+ fun resetForRead() {
+ availableForRead = totalCapacity
+ availableForWrite = 0
+ pendingToFlush = 0
+ }
+
+ fun tryReadExact(n: Int): Boolean {
+ while (true) {
+ val remaining = availableForRead
+ if (remaining < n) return false
+ if (AvailableForRead.compareAndSet(this, remaining, remaining - n)) return true
+ }
+ }
+
+ fun tryReadAtMost(n: Int): Int {
+ while (true) {
+ val remaining = availableForRead
+ val delta = minOf(n, remaining)
+ if (delta == 0) return 0
+ if (AvailableForRead.compareAndSet(this, remaining, remaining - delta)) return delta
+ }
+ }
+
+ fun tryWriteExact(n: Int): Boolean {
+ while (true) {
+ val remaining = availableForWrite
+ if (remaining < n) return false
+ if (AvailableForWrite.compareAndSet(this, remaining, remaining - n)) return true
+ }
+ }
+
+ fun tryWriteAtMost(n: Int): Int {
+ while (true) {
+ val remaining = availableForWrite
+ val delta = minOf(n, remaining)
+ if (delta == 0) return 0
+ if (AvailableForWrite.compareAndSet(this, remaining, remaining - delta)) return delta
+ }
+ }
+
+ fun completeRead(n: Int) {
+ while (true) {
+ val remaining = availableForWrite
+ val update = remaining + n
+ require(update <= totalCapacity) { "Completed read overflow: $remaining + $n = $update > $totalCapacity" }
+ if (AvailableForWrite.compareAndSet(this, remaining, update)) break
+ }
+ }
+
+ fun completeWrite(n: Int) {
+ while (true) {
+ val pending = pendingToFlush
+ val update = pending + n
+ require(update <= totalCapacity) { "Complete write overflow: $pending + $n > $totalCapacity" }
+ if (PendingToFlush.compareAndSet(this, pending, update)) break
+ }
+ }
+
+ /**
+ * @return true if there are bytes available for read after flush
+ */
+ fun flush(): Boolean {
+ val pending = PendingToFlush.getAndSet(this, 0)
+ while (true) {
+ val remaining = availableForRead
+ val update = remaining + pending
+ if (remaining == update || AvailableForRead.compareAndSet(this, remaining, update)) {
+ return update > 0
+ }
+ }
+ }
+
+ fun tryLockForRelease(): Boolean {
+ while (true) {
+ val remaining = availableForWrite
+ if (pendingToFlush > 0 || availableForRead > 0 || remaining != totalCapacity) return false
+ if (AvailableForWrite.compareAndSet(this, remaining, 0)) return true
+ }
+ }
+
+ fun isEmpty(): Boolean = availableForWrite == totalCapacity
+ fun isFull(): Boolean = availableForWrite == 0
+
+ companion object {
+ // todo: replace with atomicfu, remove companion object
+ private val AvailableForRead = intUpdater(RingBufferCapacity::availableForRead)
+ private val AvailableForWrite = intUpdater(RingBufferCapacity::availableForWrite)
+ private val PendingToFlush = intUpdater(RingBufferCapacity::pendingToFlush)
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/Strings.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/Strings.kt
new file mode 100644
index 0000000..eaff9c9
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/Strings.kt
@@ -0,0 +1,108 @@
+package kotlinx.coroutines.experimental.io.internal
+
+import java.nio.ByteBuffer
+import java.nio.charset.MalformedInputException
+
+/**
+ * Decodes all the bytes to ASCII characters until end of buffer applying every character to [consumer]
+ * It stops processing if a non-ascii character encountered and returns `false`
+ * @return `false` if a non-ascii character encountered or `true` if all bytes were processed
+ */
+internal inline fun ByteBuffer.decodeASCII(consumer: (Char) -> Boolean): Boolean {
+ while (hasRemaining()) {
+ val v = get().toInt() and 0xff
+ if (v and 0x80 != 0 || !consumer(v.toChar())) {
+ position(position() - 1)
+ return false
+ }
+ }
+
+ return true
+}
+
+/**
+ * Decodes all the bytes to utf8 applying every character on [consumer] until or consumer return `false`.
+ * If a consumer returned false then a character will be pushed back (including all surrogates will be pushed back as well)
+ * and [decodeUTF8] returns 0
+ * @return number of bytes required to decode incomplete utf8 character or 0 if all bytes were processed
+ * or -1 if consumer rejected loop
+ */
+internal inline fun ByteBuffer.decodeUTF8(consumer: (Char) -> Boolean): Int {
+ var byteCount = 0
+ var value = 0
+ var lastByteCount = 0
+
+ while (hasRemaining()) {
+ val v = get().toInt() and 0xff
+ when {
+ v and 0x80 == 0 -> {
+ if (byteCount != 0) throw MalformedInputException(0)
+ if (!consumer(v.toChar())) {
+ position(position() - 1)
+ return -1
+ }
+ }
+ byteCount == 0 -> {
+ // first unicode byte
+
+ var mask = 0x80
+ value = v
+
+ for (i in 1..6) { // TODO do we support 6 bytes unicode?
+ if (value and mask != 0) {
+ value = value and mask.inv()
+ mask = mask shr 1
+ byteCount++
+ } else {
+ break
+ }
+ }
+
+ lastByteCount = byteCount
+ byteCount--
+
+ if (byteCount > remaining()) {
+ position(position() - 1) // return one byte back
+ return lastByteCount
+ }
+ }
+ else -> {
+ // trailing unicode byte
+ value = (value shl 6) or (v and 0x7f)
+ byteCount--
+
+ if (byteCount == 0) {
+ if (isBmpCodePoint(value)) {
+ if (!consumer(value.toChar())) {
+ position(position() - lastByteCount)
+ return -1
+ }
+ } else if (!isValidCodePoint(value)) {
+ throw IllegalArgumentException("Malformed code-point ${Integer.toHexString(value)} found")
+ } else {
+ if (!consumer(highSurrogate(value).toChar()) ||
+ !consumer(lowSurrogate(value).toChar())) {
+ position(position() - lastByteCount)
+ return -1
+ }
+ }
+
+ value = 0
+ }
+ }
+ }
+ }
+
+ return 0
+}
+
+private const val MaxCodePoint = 0X10ffff
+private const val MinLowSurrogate = 0xdc00
+private const val MinHighSurrogate = 0xd800
+private const val MinSupplementary = 0x10000
+private const val HighSurrogateMagic = MinHighSurrogate - (MinSupplementary ushr 10)
+
+private fun isBmpCodePoint(cp: Int) = cp ushr 16 == 0
+private fun isValidCodePoint(codePoint: Int) = codePoint <= MaxCodePoint
+private fun lowSurrogate(cp: Int) = (cp and 0x3ff) + MinLowSurrogate
+private fun highSurrogate(cp: Int) = (cp ushr 10) + HighSurrogateMagic
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/Utils.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/Utils.kt
new file mode 100644
index 0000000..6bc92c5
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/Utils.kt
@@ -0,0 +1,85 @@
+package kotlinx.coroutines.experimental.io.internal
+
+import java.nio.*
+import java.util.concurrent.atomic.*
+import kotlin.reflect.*
+
+internal fun ByteBuffer.isEmpty() = !hasRemaining()
+
+internal inline fun <reified Owner : Any> longUpdater(p: KProperty1<Owner, Long>): AtomicLongFieldUpdater<Owner> {
+ return AtomicLongFieldUpdater.newUpdater(Owner::class.java, p.name)
+}
+
+internal inline fun <reified Owner : Any> intUpdater(p: KProperty1<Owner, Int>): AtomicIntegerFieldUpdater<Owner> {
+ return AtomicIntegerFieldUpdater.newUpdater(Owner::class.java, p.name)
+}
+
+internal inline fun <reified Owner : Any, reified T> updater(p: KProperty1<Owner, T>): AtomicReferenceFieldUpdater<Owner, T> {
+ return AtomicReferenceFieldUpdater.newUpdater(Owner::class.java, T::class.java, p.name)
+}
+
+internal fun getIOIntProperty(name: String, default: Int): Int =
+ try { System.getProperty("kotlinx.coroutines.io.$name") }
+ catch (e: SecurityException) { null }
+ ?.toIntOrNull() ?: default
+
+
+@Suppress("LoopToCallChain")
+internal fun ByteBuffer.indexOfPartial(sub: ByteBuffer): Int {
+ val subPosition = sub.position()
+ val subSize = sub.remaining()
+ val first = sub[subPosition]
+ val limit = limit()
+
+ outer@for (idx in position() until limit) {
+ if (get(idx) == first) {
+ for (j in 1 until subSize) {
+ if (idx + j == limit) break
+ if (get(idx + j) != sub.get(subPosition + j)) continue@outer
+ }
+ return idx - position()
+ }
+ }
+
+ return -1
+}
+
+@Suppress("LoopToCallChain")
+internal fun ByteBuffer.startsWith(prefix: ByteBuffer, prefixSkip: Int = 0): Boolean {
+ val size = minOf(remaining(), prefix.remaining() - prefixSkip)
+ if (size <= 0) return false
+
+ val position = position()
+ val prefixPosition = prefix.position() + prefixSkip
+
+ for (i in 0 until size) {
+ if (get(position + i) != prefix.get(prefixPosition + i)) return false
+ }
+
+ return true
+}
+
+internal fun ByteBuffer.putAtMost(src: ByteBuffer, n: Int = src.remaining()): Int {
+ val rem = remaining()
+ val srcRem = src.remaining()
+
+ return when {
+ srcRem <= rem && srcRem <= n -> {
+ put(src)
+ srcRem
+ }
+ else -> {
+ val size = minOf(rem, srcRem, n)
+ for (idx in 1..size) {
+ put(src.get())
+ }
+ size
+ }
+ }
+}
+
+internal fun ByteBuffer.putLimited(src: ByteBuffer, limit: Int = limit()): Int {
+ return putAtMost(src, limit - src.position())
+}
+
+internal fun ByteArray.asByteBuffer(offset: Int = 0, length: Int = size): ByteBuffer = ByteBuffer.wrap(this, offset, length)
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacket.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacket.kt
new file mode 100644
index 0000000..f24b8bb
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacket.kt
@@ -0,0 +1,44 @@
+package kotlinx.coroutines.experimental.io.packet
+
+import java.io.*
+import java.nio.ByteBuffer
+import kotlin.experimental.and
+
+interface ByteReadPacket {
+ val remaining: Int
+
+ fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int
+ fun readAvailable(dst: ByteArray) = readAvailable(dst, 0, dst.size)
+ fun readAvailable(dst: ByteBuffer): Int
+
+ fun readFully(dst: ByteArray, offset: Int, length: Int)
+ fun readFully(dst: ByteArray) = readFully(dst, 0, dst.size)
+ fun readFully(dst: ByteBuffer): Int
+
+ fun readLong(): Long
+ fun readInt(): Int
+ fun readShort(): Short
+ fun readByte(): Byte
+
+ fun readUInt(): Long = readInt().toLong() and 0xffffffff
+ fun readUShort(): Int = readShort().toInt() and 0xffff
+ fun readUByte(): Short = readByte().toShort() and 0xff
+
+ fun readDouble(): Double
+ fun readFloat(): Float
+
+ fun skip(n: Int): Int
+ fun skipExact(n: Int) {
+ if (skip(n) != n) throw EOFException("Unable to skip $n bytes due to end of packet")
+ }
+
+ fun release()
+ fun copy(): ByteReadPacket
+
+ fun readUTF8LineTo(out: Appendable, limit: Int = Int.MAX_VALUE): Boolean
+
+ fun inputStream(): InputStream
+ fun readerUTF8(): Reader
+
+ fun readText(): CharSequence
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketEmpty.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketEmpty.kt
new file mode 100644
index 0000000..36a264d
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketEmpty.kt
@@ -0,0 +1,88 @@
+package kotlinx.coroutines.experimental.io.packet
+
+import java.io.*
+import java.nio.*
+
+object ByteReadPacketEmpty : ByteReadPacket {
+ override val remaining: Int
+ get() = 0
+
+ override fun readAvailable(dst: ByteArray, offset: Int, length: Int) = -1
+ override fun readAvailable(dst: ByteBuffer): Int = -1
+
+ override fun readFully(dst: ByteArray, offset: Int, length: Int) {
+ if (length > 0) throw EOFException("Couldn't read $length bytes from empty packet")
+ }
+
+ override fun readFully(dst: ByteBuffer): Int {
+ if (dst.hasRemaining()) throw EOFException("Couldn't read ${dst.remaining()} bytes from empty packet")
+ return 0
+ }
+
+ override fun readLong(): Long {
+ throw EOFException("Couldn't read long from empty packet")
+ }
+
+ override fun readInt(): Int {
+ throw EOFException("Couldn't read int from empty packet")
+ }
+
+ override fun readShort(): Short {
+ throw EOFException("Couldn't read short from empty packet")
+ }
+
+ override fun readByte(): Byte {
+ throw EOFException("Couldn't read byte from empty packet")
+ }
+
+ override fun readDouble(): Double {
+ throw EOFException("Couldn't read double from empty packet")
+ }
+
+ override fun readFloat(): Float {
+ throw EOFException("Couldn't read float from empty packet")
+ }
+
+ override fun skip(n: Int) = 0
+
+ override fun release() {
+ }
+
+ override fun copy() = this
+
+ override fun readUTF8LineTo(out: Appendable, limit: Int) = false
+
+ override fun inputStream(): InputStream = EmptyInputStream
+ override fun readerUTF8(): Reader = EmptyReader
+
+ override fun readText() = ""
+
+ private object EmptyInputStream : InputStream() {
+ override fun available() = 0
+
+ override fun read(): Int = -1
+ override fun read(b: ByteArray?, off: Int, len: Int): Int = -1
+ override fun read(b: ByteArray?) = -1
+
+ override fun skip(n: Long) = 0L
+
+ override fun markSupported() = true
+ override fun mark(readlimit: Int) {
+ }
+
+ override fun reset() {
+ }
+
+ override fun close() {
+ }
+ }
+
+ private object EmptyReader : Reader() {
+ override fun close() {
+ }
+
+ override fun read(cbuf: CharArray?, off: Int, len: Int) = -1
+ override fun read() = -1
+ override fun read(target: CharBuffer?) = -1
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketImpl.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketImpl.kt
new file mode 100644
index 0000000..520dc0d
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketImpl.kt
@@ -0,0 +1,301 @@
+@file:Suppress("UsePropertyAccessSyntax")
+
+package kotlinx.coroutines.experimental.io.packet
+
+import kotlinx.coroutines.experimental.io.internal.*
+import java.io.*
+import java.nio.*
+import java.nio.charset.*
+import java.util.*
+
+internal class ByteReadPacketImpl(internal val packets: ArrayDeque<ByteBuffer>, internal val pool: ObjectPool<ByteBuffer>) : ByteReadPacket {
+ override val remaining: Int
+ get() = packets.sumBy { it.remaining() }
+
+ internal fun steal(): ByteBuffer = packets.pollFirst() ?: throw IllegalStateException("EOF")
+
+ override fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int {
+ var copied = 0
+
+ val rc = reading(0) { buffer ->
+ val size = minOf(buffer.remaining(), length - copied)
+ buffer.get(dst, offset + copied, size)
+ copied += size
+
+ copied < length
+ }
+
+ return if (rc) copied else -1
+ }
+
+ override fun readAvailable(dst: ByteBuffer): Int {
+ var copied = 0
+
+ val rc = reading(0) { buffer ->
+ if (dst.remaining() >= buffer.remaining()) {
+ copied += buffer.remaining()
+ dst.put(buffer)
+ } else {
+ while (dst.hasRemaining() && buffer.hasRemaining()) {
+ dst.put(buffer.get())
+ copied++
+ }
+ }
+
+ dst.hasRemaining()
+ }
+
+ return if (rc) copied else -1
+ }
+
+ override fun readFully(dst: ByteArray, offset: Int, length: Int) {
+ val rc = readAvailable(dst, offset, length)
+ if (rc == -1 && length == 0) return
+ if (rc < length) throw EOFException("Not enough bytes in the packet")
+ }
+
+ override fun readFully(dst: ByteBuffer): Int {
+ val rc = readAvailable(dst)
+ if (dst.hasRemaining()) throw EOFException("Not enough bytes in the packet")
+ return rc
+ }
+
+ override fun readLong(): Long {
+ var v = 0L
+ val rc = reading(8) { v = it.getLong(); false }
+ if (!rc) throw EOFException("Couldn't read long from empty packet")
+ return v
+ }
+
+ override fun readInt(): Int {
+ var v = 0
+ val rc = reading(4) { v = it.getInt(); false }
+ if (!rc) throw EOFException("Couldn't read int from empty packet")
+ return v
+ }
+
+ override fun readShort(): Short {
+ var v: Short = 0
+ val rc = reading(2) { v = it.getShort(); false }
+ if (!rc) throw EOFException("Couldn't read short from empty packet")
+ return v
+ }
+
+ override fun readByte(): Byte {
+ var v: Byte = 0
+ val rc = reading(1) { v = it.get(); false }
+ if (!rc) throw EOFException("Couldn't read byte from empty packet")
+ return v
+ }
+
+ override fun readDouble(): Double {
+ var v = 0.0
+ val rc = reading(8) { v = it.getDouble(); false }
+ if (!rc) throw EOFException("Couldn't read double from empty packet")
+ return v
+ }
+
+ override fun readFloat(): Float {
+ var v = 0.0f
+ val rc = reading(4) { v = it.getFloat(); false }
+ if (!rc) throw EOFException("Couldn't read float from empty packet")
+ return v
+ }
+
+ override fun readUTF8LineTo(out: Appendable, limit: Int): Boolean {
+ var decoded = 0
+ var size = 1
+ var cr = false
+ var end = false
+
+ val rc = reading(size) { bb ->
+ size = bb.decodeUTF8 { ch ->
+ when (ch) {
+ '\r' -> {
+ if (cr) {
+ end = true
+ return@decodeUTF8 false
+ }
+ cr = true
+ true
+ }
+ '\n' -> {
+ return true
+ }
+ else -> {
+ if (cr) {
+ end = true
+ return@decodeUTF8 false
+ }
+
+ if (decoded == limit) throw BufferOverflowException()
+ decoded++
+ out.append(ch)
+ true
+ }
+ }
+ }
+
+ !end && size == 0
+ }
+
+ if (rc && size > 0) throw MalformedInputException(0)
+
+ return rc
+ }
+
+ override fun readText(): CharSequence {
+ val sb = StringBuilder(remaining)
+
+ var size = 0
+
+ reading(1) { bb ->
+ size = bb.decodeUTF8 { ch ->
+ sb.append(ch)
+ true
+ }
+
+ size == 0
+ }
+
+ if (size > 0) throw CharacterCodingException()
+
+ return sb
+ }
+
+ override fun skip(n: Int): Int {
+ var skipped = 0
+
+ reading(0) {
+ val m = minOf(n - skipped, it.remaining())
+ it.position(it.position() + m)
+ skipped += m
+
+ skipped < n
+ }
+
+ return skipped
+ }
+
+ override fun inputStream(): InputStream {
+ return object : InputStream() {
+ override fun read(): Int {
+ var v: Byte = 0
+ val rc = reading(1) { v = it.get(); true }
+ return if (rc) v.toInt() and 0xff else -1
+ }
+
+ override fun read(b: ByteArray, off: Int, len: Int) = readAvailable(b, off, len)
+ override fun skip(n: Long): Long {
+ if (n > Int.MAX_VALUE) return this@ByteReadPacketImpl.skip(Int.MAX_VALUE).toLong()
+ return this@ByteReadPacketImpl.skip(n.toInt()).toLong()
+ }
+
+ override fun available() = remaining
+ }
+ }
+
+ override fun readerUTF8(): Reader {
+ return object : Reader() {
+ override fun close() {
+ release()
+ }
+
+ override fun read(cbuf: CharArray, off: Int, len: Int): Int {
+ var decoded = 0
+ var size = 1
+
+ val rc = reading(size) { bb ->
+ size = bb.decodeUTF8 { ch ->
+ if (decoded == len) false
+ else {
+ cbuf[off + decoded] = ch
+ decoded++
+ true
+ }
+ }
+
+ size == 0
+ }
+
+ return when {
+ rc && size > 0 -> throw CharacterCodingException()
+ rc -> decoded
+ else -> -1
+ }
+ }
+ }
+ }
+
+ override fun release() {
+ while (packets.isNotEmpty()) {
+ recycle(packets.remove())
+ }
+ }
+
+ override fun copy(): ByteReadPacket {
+ if (packets.isEmpty()) return ByteReadPacketEmpty
+ val copyDeque = ArrayDeque<ByteBuffer>(packets.size)
+
+ for (p in packets) {
+ copyDeque.add(pool.borrow().also { it.put(p.duplicate()); it.flip() })
+ }
+
+ return ByteReadPacketImpl(copyDeque, pool)
+ }
+
+ private inline fun reading(size: Int, block: (ByteBuffer) -> Boolean): Boolean {
+ if (packets.isEmpty()) return false
+
+ var visited = false
+ var buffer = packets.peekFirst()
+ var stop = false
+
+ while (!stop) {
+ if (buffer.hasRemaining()) {
+ if (buffer.remaining() < size) {
+ if (!tryStealBytesFromNextBuffer(size, buffer)) return false
+ }
+
+ visited = true
+ stop = !block(buffer)
+ }
+
+ if (!buffer.hasRemaining()) {
+ packets.removeFirst()
+ recycle(buffer)
+
+ if (packets.isEmpty()) break
+ buffer = packets.peekFirst()
+ }
+ }
+
+ return visited
+ }
+
+ private fun tryStealBytesFromNextBuffer(size: Int, buffer: ByteBuffer): Boolean {
+ if (packets.size == 1) {
+ return false
+ }
+
+ packets.removeFirst()
+
+ val extraBytes = size - buffer.remaining()
+ val next = packets.peekFirst()
+
+ if (extraBytes > next.remaining()) return false
+
+ buffer.compact()
+ repeat(extraBytes) {
+ buffer.put(next.get())
+ }
+ buffer.flip()
+
+ packets.addFirst(buffer)
+ return true
+ }
+
+ private fun recycle(buffer: ByteBuffer) {
+ pool.recycle(buffer)
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketSingle.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketSingle.kt
new file mode 100644
index 0000000..96518e9
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteReadPacketSingle.kt
@@ -0,0 +1,256 @@
+package kotlinx.coroutines.experimental.io.packet
+
+import kotlinx.coroutines.experimental.io.internal.*
+import java.io.*
+import java.nio.*
+import java.nio.charset.*
+
+internal class ByteReadPacketSingle(internal var buffer: ByteBuffer?, internal val pool: ObjectPool<ByteBuffer>) : ByteReadPacket {
+ override val remaining: Int
+ get() = buffer?.remaining() ?: 0
+
+ internal fun steal(): ByteBuffer = buffer?.also { buffer = null } ?: throw IllegalStateException("EOF")
+
+ override fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int {
+ var copied = 0
+
+ val rc = reading { buffer ->
+ val size = minOf(buffer.remaining(), length - copied)
+ buffer.get(dst, offset, size)
+ copied += size
+
+ if (copied == length) return copied
+ }
+
+ return if (rc) copied else -1
+ }
+
+ override fun readAvailable(dst: ByteBuffer): Int {
+ var copied = 0
+
+ val rc = reading { buffer ->
+ if (dst.remaining() >= buffer.remaining()) {
+ copied += buffer.remaining()
+ dst.put(buffer)
+ } else {
+ while (dst.hasRemaining() && buffer.hasRemaining()) {
+ dst.put(buffer.get())
+ copied++
+ }
+ }
+
+ if (!dst.hasRemaining()) return copied
+ }
+
+ return if (rc) copied else -1
+ }
+
+ override fun readFully(dst: ByteArray, offset: Int, length: Int) {
+ val rc = readAvailable(dst, offset, length)
+ if (rc < length) throw EOFException("Not enough bytes in the packet")
+ }
+
+ override fun readFully(dst: ByteBuffer): Int {
+ val rc = readAvailable(dst)
+ if (dst.hasRemaining()) throw EOFException("Not enough bytes in the packet")
+ return rc
+ }
+
+ override fun readLong(): Long {
+ var v = 0L
+ val rc = reading { v = it.getLong() }
+ if (!rc) throw EOFException("Couldn't read long from empty packet")
+ return v
+ }
+
+ override fun readInt(): Int {
+ var v = 0
+ val rc = reading { v = it.getInt() }
+ if (!rc) throw EOFException("Couldn't read int from empty packet")
+ return v
+ }
+
+ override fun readShort(): Short {
+ var v: Short = 0
+ val rc = reading { v = it.getShort() }
+ if (!rc) throw EOFException("Couldn't read short from empty packet")
+ return v
+ }
+
+ override fun readByte(): Byte {
+ var v: Byte = 0
+ val rc = reading { v = it.get() }
+ if (!rc) throw EOFException("Couldn't read byte from empty packet")
+ return v
+ }
+
+ override fun readDouble(): Double {
+ var v = 0.0
+ val rc = reading { v = it.getDouble() }
+ if (!rc) throw EOFException("Couldn't read double from empty packet")
+ return v
+ }
+
+ override fun readFloat(): Float {
+ var v = 0.0f
+ val rc = reading { v = it.getFloat() }
+ if (!rc) throw EOFException("Couldn't read float from empty packet")
+ return v
+ }
+
+ override fun readUTF8LineTo(out: Appendable, limit: Int): Boolean {
+ var decoded = 0
+ var cr = false
+
+ return reading { bb ->
+ val rc = bb.decodeUTF8 { ch ->
+ when (ch) {
+ '\r' -> {
+ if (cr) {
+ false
+ } else {
+ cr = true
+ true
+ }
+ }
+ '\n' -> false
+ else -> {
+ if (cr) {
+ false
+ } else {
+ if (decoded == limit) throw BufferOverflowException()
+ decoded++
+ out.append(ch)
+ true
+ }
+ }
+ }
+ }
+
+ if (rc == -1) {
+ val v = bb.get()
+ if (v != 0x0a.toByte() && v != 0x0d.toByte()) {
+ bb.position(bb.position() - 1)
+ }
+ } else if (rc > 0) throw MalformedInputException(0)
+ }
+ }
+
+ override fun readText(): CharSequence {
+ val sb = StringBuilder(remaining)
+
+ var size = 0
+
+ reading { bb ->
+ size = bb.decodeUTF8 { ch ->
+ sb.append(ch)
+ true
+ }
+
+ size == 0
+ }
+
+ if (size > 0) throw CharacterCodingException()
+
+ return sb
+ }
+
+ override fun skip(n: Int): Int {
+ var skipped = 0
+
+ reading {
+ val m = minOf(n - skipped, it.remaining())
+ it.position(it.position() + m)
+ skipped += m
+ }
+
+ return skipped
+ }
+
+ override fun inputStream(): InputStream {
+ return object : InputStream() {
+ override fun read(): Int {
+ var v: Byte = 0
+ val rc = reading { v = it.get() }
+ return if (rc) v.toInt() and 0xff else -1
+ }
+
+ override fun read(b: ByteArray, off: Int, len: Int) = readAvailable(b, off, len)
+ override fun skip(n: Long): Long {
+ if (n > Int.MAX_VALUE) return this@ByteReadPacketSingle.skip(Int.MAX_VALUE).toLong()
+ return this@ByteReadPacketSingle.skip(n.toInt()).toLong()
+ }
+
+ override fun available() = remaining
+
+ override fun close() {
+ release()
+ }
+ }
+ }
+
+ override fun readerUTF8(): Reader {
+ return object : Reader() {
+ override fun close() {
+ release()
+ }
+
+ override fun read(cbuf: CharArray, off: Int, len: Int): Int {
+ var decoded = 0
+
+ val rc = reading { bb ->
+ if (len == 0) return@reading
+
+ val rc = bb.decodeUTF8 { ch ->
+ if (decoded == len) false
+ else {
+ cbuf[off + decoded] =ch
+ decoded++
+ true
+ }
+ }
+
+ if (rc > 0) throw MalformedInputException(0)
+ }
+
+ return if (!rc) -1 else decoded
+ }
+ }
+ }
+
+ override fun release() {
+ recycle(buffer ?: return)
+ buffer = null
+ }
+
+ override fun copy(): ByteReadPacket {
+ val bb = buffer?.takeIf { it.hasRemaining() } ?: return ByteReadPacketEmpty
+ val copyBuffer = pool.borrow()
+ copyBuffer.put(bb.duplicate())
+ copyBuffer.flip()
+ return ByteReadPacketSingle(copyBuffer, pool)
+ }
+
+ private inline fun reading(block: (ByteBuffer) -> Unit): Boolean {
+ val buffer = buffer ?: return false
+
+ if (!buffer.hasRemaining()) {
+ this.buffer = null
+ recycle(buffer)
+ return false
+ }
+
+ block(buffer)
+
+ if (!buffer.hasRemaining()) {
+ this.buffer = null
+ recycle(buffer)
+ }
+
+ return true
+ }
+
+ private fun recycle(buffer: ByteBuffer) {
+ pool.recycle(buffer)
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteWritePacket.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteWritePacket.kt
new file mode 100644
index 0000000..9391647
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteWritePacket.kt
@@ -0,0 +1,38 @@
+package kotlinx.coroutines.experimental.io.packet
+
+import java.io.*
+import java.nio.ByteBuffer
+import java.nio.CharBuffer
+
+interface ByteWritePacket : Appendable {
+ val size: Int
+
+ fun writeFully(src: ByteArray, offset: Int, length: Int)
+ fun writeFully(src: ByteArray) = writeFully(src, 0, src.size)
+ fun writeFully(src: ByteBuffer)
+
+ fun writeLong(l: Long)
+ fun writeInt(i: Int)
+ fun writeShort(s: Short)
+ fun writeByte(b: Byte)
+ fun writeDouble(d: Double)
+ fun writeFloat(f: Float)
+
+ fun writeStringUtf8(s: String)
+ fun writeStringUtf8(cb: CharBuffer)
+ fun writeStringUtf8(cs: CharSequence)
+
+ fun release()
+ fun build(): ByteReadPacket
+
+ fun outputStream(): OutputStream
+ fun writerUTF8(): Writer
+
+ fun writePacket(p: ByteReadPacket)
+ fun writePacketUnconsumed(p: ByteReadPacket)
+
+ override fun append(csq: CharSequence): ByteWritePacket {
+ append(csq, 0, csq.length)
+ return this
+ }
+}
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteWritePacketImpl.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteWritePacketImpl.kt
new file mode 100644
index 0000000..e969e07
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/ByteWritePacketImpl.kt
@@ -0,0 +1,343 @@
+package kotlinx.coroutines.experimental.io.packet
+
+import kotlinx.coroutines.experimental.io.internal.ObjectPool
+import java.io.*
+import java.nio.ByteBuffer
+import java.nio.CharBuffer
+import java.util.*
+
+internal class ByteWritePacketImpl(private val pool: ObjectPool<ByteBuffer>) : ByteWritePacket {
+ override var size: Int = 0
+ private set
+ private var buffers: Any? = null
+
+ override fun writeFully(src: ByteArray, offset: Int, length: Int) {
+ var copied = 0
+
+ while (copied < length) {
+ write(1) { buffer ->
+ val size = minOf(buffer.remaining(), length - copied)
+ buffer.put(src, offset + copied, size)
+ copied += size
+ }
+ }
+
+ size += length
+ }
+
+ override fun writeFully(src: ByteBuffer) {
+ val s = src.remaining()
+ while (src.hasRemaining()) {
+ write(1) { buffer ->
+ if (buffer.remaining() >= src.remaining()) {
+ buffer.put(src)
+ } else {
+ while (buffer.hasRemaining() && src.hasRemaining()) {
+ buffer.put(src.get())
+ }
+ }
+ }
+ }
+ size += s
+ }
+
+ override fun writeLong(l: Long) {
+ write(8) { it.putLong(l) }
+ size += 8
+ }
+
+ override fun writeInt(i: Int) {
+ write(4) { it.putInt(i) }
+ size += 4
+ }
+
+ override fun writeShort(s: Short) {
+ write(2) { it.putShort(s) }
+ size += 2
+ }
+
+ override fun writeByte(b: Byte) {
+ write(1) { it.put(b) }
+ size += 1
+ }
+
+ override fun writeDouble(d: Double) {
+ write(8) { it.putDouble(d) }
+ size += 8
+ }
+
+ override fun writeFloat(f: Float) {
+ write(4) { it.putFloat(f) }
+ size += 4
+ }
+
+ override fun append(c: Char): ByteWritePacket {
+ write(3) {
+ size += it.putUtf8Char(c.toInt() and 0xffff)
+ }
+ return this
+ }
+
+ override fun append(csq: CharSequence, start: Int, end: Int): ByteWritePacket {
+ appendASCII(csq, start, end)
+ return this
+ }
+
+ override fun writePacket(p: ByteReadPacket) {
+ when (p) {
+ is ByteReadPacketEmpty -> {}
+ is ByteReadPacketSingle -> {
+ if (p.remaining > 0) {
+ size += p.remaining
+ last(p.steal().also { it.compact() })
+ }
+ }
+ is ByteReadPacketImpl -> {
+ size += p.remaining
+ while (p.remaining > 0) {
+ last(p.steal().also { it.compact() })
+ }
+ }
+ else -> {
+ writeFully(p.readBytes())
+ }
+ }
+ }
+
+ override fun writePacketUnconsumed(p: ByteReadPacket) {
+ when (p) {
+ is ByteReadPacketEmpty -> {}
+ is ByteReadPacketSingle -> {
+ p.buffer?.duplicate()?.let { writeFully(it) }
+ }
+ is ByteReadPacketImpl -> {
+ for (buffer in p.packets) {
+ writeFully(buffer.duplicate())
+ }
+ }
+ else -> throw UnsupportedOperationException()
+ }
+ }
+
+ private tailrec fun appendASCII(csq: CharSequence, start: Int, end: Int) {
+ val bb = ensure()
+ val limitedEnd = minOf(end, start + bb.remaining())
+
+ for (i in start until limitedEnd) {
+ val chi = csq[i].toInt() and 0xffff
+ if (chi >= 0x80) {
+ appendUTF8(csq, i, end, bb)
+ return
+ }
+
+ bb.put(chi.toByte())
+ size++
+ }
+
+ if (limitedEnd < end) {
+ return appendASCII(csq, limitedEnd, end)
+ }
+ }
+
+ // expects at least one byte remaining in [bb]
+ private tailrec fun appendUTF8(csq: CharSequence, start: Int, end: Int, bb: ByteBuffer) {
+ val limitedEnd = minOf(end, start + bb.remaining())
+
+ for (i in start until limitedEnd) {
+ val chi = csq[i].toInt() and 0xffff
+ val requiredSize = when {
+ chi <= 0x7f -> 1
+ chi > 0x7ff -> 3
+ else -> 2
+ }
+
+ if (bb.remaining() < requiredSize) {
+ return appendUTF8(csq, i, end, appendNewBuffer())
+ }
+
+ size += bb.putUtf8Char(chi)
+ }
+
+ if (limitedEnd < end) {
+ return appendUTF8(csq, limitedEnd, end, appendNewBuffer())
+ }
+ }
+
+ private tailrec fun appendASCII(csq: CharArray, start: Int, end: Int) {
+ val bb = ensure()
+ val limitedEnd = minOf(end, start + bb.remaining())
+
+ for (i in start until limitedEnd) {
+ val chi = csq[i].toInt() and 0xffff
+ if (chi >= 0x80) {
+ appendUTF8(csq, i, end, bb)
+ return
+ }
+
+ bb.put(chi.toByte())
+ size++
+ }
+
+ if (limitedEnd < end) {
+ return appendASCII(csq, limitedEnd, end)
+ }
+ }
+
+ // expects at least one byte remaining in [bb]
+ private tailrec fun appendUTF8(csq: CharArray, start: Int, end: Int, bb: ByteBuffer) {
+ val limitedEnd = minOf(end, start + bb.remaining())
+ for (i in start until limitedEnd) {
+ val chi = csq[i].toInt() and 0xffff
+ val requiredSize = when {
+ chi <= 0x7f -> 1
+ chi > 0x7ff -> 3
+ else -> 2
+ }
+
+ if (bb.remaining() < requiredSize) {
+ return appendUTF8(csq, i, end, appendNewBuffer())
+ }
+
+ size += bb.putUtf8Char(chi)
+ }
+
+ if (limitedEnd < end) {
+ return appendUTF8(csq, limitedEnd, end, appendNewBuffer())
+ }
+ }
+
+ override fun writeStringUtf8(s: String) {
+ append(s, 0, s.length)
+ }
+
+ override fun writeStringUtf8(cs: CharSequence) {
+ append(cs, 0, cs.length)
+ }
+
+ override fun writeStringUtf8(cb: CharBuffer) {
+ append(cb, 0, cb.remaining())
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ private inline fun ByteBuffer.putUtf8Char(v: Int) = when {
+ v in 1..0x7f -> {
+ put(v.toByte())
+ 1
+ }
+ v > 0x7ff -> {
+ put((0xe0 or ((v shr 12) and 0x0f)).toByte())
+ put((0x80 or ((v shr 6) and 0x3f)).toByte())
+ put((0x80 or ( v and 0x3f)).toByte())
+ 3
+ }
+ else -> {
+ put((0xc0 or ((v shr 6) and 0x1f)).toByte())
+ put((0x80 or ( v and 0x3f)).toByte())
+ 2
+ }
+ }
+
+ override fun outputStream(): OutputStream {
+ return object : OutputStream() {
+ override fun write(b: Int) {
+ writeByte(b.toByte())
+ }
+
+ override fun write(b: ByteArray, off: Int, len: Int) {
+ writeFully(b, off, len)
+ }
+ }
+ }
+
+ override fun writerUTF8(): Writer {
+ return object : Writer() {
+ override fun write(cbuf: CharArray, off: Int, len: Int) {
+ appendASCII(cbuf, off, len)
+ }
+
+ override fun flush() {
+ }
+
+ override fun close() {
+ }
+ }
+ }
+
+ override fun build(): ByteReadPacket {
+ val bs = buffers ?: return ByteReadPacketEmpty
+ buffers = null
+
+ return if (bs is ArrayDeque<*>) {
+ @Suppress("UNCHECKED_CAST")
+ when {
+ bs.isEmpty() -> ByteReadPacketEmpty
+ bs.size == 1 -> ByteReadPacketSingle((bs.first as ByteBuffer).also { it.flip() }, pool)
+ else -> ByteReadPacketImpl((bs as ArrayDeque<ByteBuffer>).also {
+ for (b in bs) {
+ b.flip()
+ }
+ }, pool)
+ }
+ } else {
+ ByteReadPacketSingle((bs as ByteBuffer).also { it.flip() }, pool)
+ }
+ }
+
+ override fun release() {
+ val bs = buffers ?: return
+ buffers = null
+ size = 0
+
+ if (bs is ArrayDeque<*>) {
+ for (o in bs) {
+ recycle(o as ByteBuffer)
+ }
+ } else {
+ recycle(bs as ByteBuffer)
+ }
+ }
+
+ private inline fun write(size: Int, block: (ByteBuffer) -> Unit) {
+ val buffer = last()?.takeIf { it.remaining() >= size }
+
+ if (buffer == null) {
+ block(appendNewBuffer())
+ } else {
+ block(buffer)
+ }
+ }
+
+ private fun ensure(): ByteBuffer = last()?.takeIf { it.hasRemaining() } ?: appendNewBuffer()
+
+ private fun appendNewBuffer(): ByteBuffer {
+ val new = pool.borrow()
+ new.clear()
+ last(new)
+ return new
+ }
+
+ private fun last(): ByteBuffer? = buffers?.let { b ->
+ @Suppress("UNCHECKED_CAST")
+ when (b) {
+ is ByteBuffer -> b
+ is ArrayDeque<*> -> (b as ArrayDeque<ByteBuffer>).takeIf { it.isNotEmpty() }?.peekLast()
+ else -> null
+ }
+ }
+
+ private fun last(new: ByteBuffer) {
+ @Suppress("UNCHECKED_CAST")
+ if (buffers is ArrayDeque<*>) (buffers as ArrayDeque<ByteBuffer>).addLast(new)
+ else if (buffers == null) buffers = new
+ else {
+ val dq = ArrayDeque<ByteBuffer>()
+ dq.addFirst(buffers as ByteBuffer)
+ dq.addLast(new)
+ buffers = dq
+ }
+ }
+
+ private fun recycle(buffer: ByteBuffer) {
+ pool.recycle(buffer)
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/Packets.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/Packets.kt
new file mode 100644
index 0000000..c9b809a
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/packet/Packets.kt
@@ -0,0 +1,25 @@
+package kotlinx.coroutines.experimental.io.packet
+
+import kotlinx.coroutines.experimental.io.internal.*
+import java.nio.ByteBuffer
+
+internal val PACKET_BUFFER_SIZE = getIOIntProperty("PacketBufferSize", 4096)
+private val PACKET_BUFFER_POOL_SIZE = getIOIntProperty("PacketBufferPoolSize", 128)
+
+private val PacketBufferPool: ObjectPool<ByteBuffer> =
+ object : ObjectPoolImpl<ByteBuffer>(PACKET_BUFFER_POOL_SIZE) {
+ override fun produceInstance(): ByteBuffer = ByteBuffer.allocateDirect(PACKET_BUFFER_SIZE)
+ override fun clearInstance(instance: ByteBuffer) = instance.apply { clear() }
+ }
+
+inline fun buildPacket(block: ByteWritePacket.() -> Unit): ByteReadPacket =
+ WritePacket().apply(block).build()
+
+fun WritePacket(): ByteWritePacket = ByteWritePacketImpl(PacketBufferPool)
+
+fun ByteReadPacket.readUTF8Line(estimate: Int = 16, limit: Int = Int.MAX_VALUE): String? {
+ val sb = StringBuilder(estimate)
+ return if (readUTF8LineTo(sb, limit)) sb.toString() else null
+}
+
+fun ByteReadPacket.readBytes(n: Int = remaining): ByteArray = ByteArray(n).also { readFully(it) }
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannelScenarioTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannelScenarioTest.kt
new file mode 100644
index 0000000..fe70122
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannelScenarioTest.kt
@@ -0,0 +1,223 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.TestBase
+import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
+import kotlinx.coroutines.experimental.yield
+import org.junit.Test
+import java.io.IOException
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+class ByteBufferChannelScenarioTest : TestBase() {
+ private val ch = ByteBufferChannel(true)
+
+ @Test
+ fun testReadBeforeAvailable() {
+ expect(1)
+
+ runBlocking {
+ launch(coroutineContext) {
+ expect(3)
+
+ val bb = ByteBuffer.allocate(10)
+ val rc = ch.readAvailable(bb) // should suspend
+
+ expect(5)
+ assertEquals(4, rc)
+
+ expect(6)
+ }
+
+ expect(2)
+ yield()
+
+ expect(4)
+ ch.writeInt(0xff) // should resume
+
+ yield()
+
+ finish(7)
+ }
+ }
+
+ @Test
+ fun testReadBeforeAvailable2() {
+ expect(1)
+
+ runBlocking {
+ launch(coroutineContext) {
+ expect(3)
+
+ val bb = ByteBuffer.allocate(4)
+ ch.readFully(bb) // should suspend
+
+ expect(5)
+
+ bb.flip()
+ assertEquals(4, bb.remaining())
+
+ expect(6)
+ }
+
+ expect(2)
+ yield()
+
+ expect(4)
+ ch.writeInt(0xff) // should resume
+
+ yield()
+
+ finish(7)
+ }
+ }
+
+ @Test
+ fun testReadAfterAvailable() {
+ expect(1)
+
+ runBlocking {
+ ch.writeInt(0xff) // should resume
+
+ launch(coroutineContext) {
+ expect(3)
+
+ val bb = ByteBuffer.allocate(10)
+ val rc = ch.readAvailable(bb) // should NOT suspend
+
+ expect(4)
+ assertEquals(4, rc)
+
+ expect(5)
+ }
+
+ expect(2)
+ yield()
+
+ finish(6)
+ }
+ }
+
+ @Test
+ fun testReadAfterAvailable2() {
+ expect(1)
+
+ runBlocking {
+ ch.writeInt(0xff) // should resume
+
+ launch(coroutineContext) {
+ expect(3)
+
+ val bb = ByteBuffer.allocate(4)
+ ch.readFully(bb) // should NOT suspend
+
+ expect(4)
+ bb.flip()
+ assertEquals(4, bb.remaining())
+
+ expect(5)
+ }
+
+ expect(2)
+ yield()
+
+ finish(6)
+ }
+ }
+
+ @Test
+ fun testReadToEmpty() {
+ runBlocking {
+ expect(1)
+
+ val rc = ch.readAvailable(ByteBuffer.allocate(0))
+
+ expect(2)
+
+ assertEquals(0, rc)
+
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testReadToEmptyFromFailedChannel() {
+ runBlocking {
+ expect(1)
+
+ ch.close(IOException())
+
+ try {
+ ch.readAvailable(ByteBuffer.allocate(0))
+ fail("Should throw exception")
+ } catch (expected: IOException) {
+ }
+
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testReadToEmptyFromClosedChannel() {
+ runBlocking {
+ expect(1)
+
+ ch.close()
+
+ val rc = ch.readAvailable(ByteBuffer.allocate(0))
+
+ expect(2)
+
+ assertEquals(-1, rc)
+
+ finish(3)
+ }
+ }
+
+ @Test
+ fun testReadFullyToEmptyFromClosedChannel() {
+ runBlocking {
+ expect(1)
+
+ ch.close()
+
+ ch.readFully(ByteBuffer.allocate(0))
+
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testReadFullyFromClosedChannel() {
+ runBlocking {
+ expect(1)
+
+ ch.close()
+ try {
+ ch.readFully(ByteBuffer.allocate(1))
+ fail("Should throw exception")
+ } catch (expected: ClosedReceiveChannelException) {
+ }
+
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testReadFullyToEmptyFromFailedChannel() {
+ runBlocking {
+ expect(1)
+
+ ch.close(IOException())
+
+ try {
+ ch.readFully(ByteBuffer.allocate(0))
+ fail("Should throw exception")
+ } catch (expected: IOException) {
+ }
+
+ finish(2)
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannelTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannelTest.kt
new file mode 100644
index 0000000..9f75542
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannelTest.kt
@@ -0,0 +1,615 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.CommonPool
+import kotlinx.coroutines.experimental.CoroutineName
+import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException
+import kotlinx.coroutines.experimental.io.internal.BUFFER_SIZE
+import kotlinx.coroutines.experimental.io.internal.BufferObjectNoPool
+import kotlinx.coroutines.experimental.io.internal.RESERVED_SIZE
+import kotlinx.coroutines.experimental.io.packet.buildPacket
+import kotlinx.coroutines.experimental.io.packet.readUTF8Line
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ErrorCollector
+import org.junit.rules.Timeout
+import java.nio.ByteBuffer
+import java.nio.CharBuffer
+import java.util.*
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+class ByteBufferChannelTest {
+ @get:Rule
+ val timeout = Timeout(10, TimeUnit.SECONDS)
+
+ @get:Rule
+ val failures = ErrorCollector()
+
+ private val Size = BUFFER_SIZE - RESERVED_SIZE
+ private val ch = ByteBufferChannel(autoFlush = false, pool = BufferObjectNoPool)
+
+ @Test
+ fun testBoolean() {
+ runBlocking {
+ ch.writeBoolean(true)
+ ch.flush()
+ assertEquals(true, ch.readBoolean())
+
+ ch.writeBoolean(false)
+ ch.flush()
+ assertEquals(false, ch.readBoolean())
+ }
+ }
+
+ @Test
+ fun testByte() {
+ runBlocking {
+ assertEquals(0, ch.availableForRead)
+ ch.writeByte(-1)
+ ch.flush()
+ assertEquals(1, ch.availableForRead)
+ assertEquals(-1, ch.readByte())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testShortB() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.BIG_ENDIAN
+ ch.writeByteOrder = ByteOrder.BIG_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeShort(-1)
+ assertEquals(0, ch.availableForRead)
+ ch.flush()
+ assertEquals(2, ch.availableForRead)
+ assertEquals(-1, ch.readShort())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testShortL() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.LITTLE_ENDIAN
+ ch.writeByteOrder = ByteOrder.LITTLE_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeShort(-1)
+ assertEquals(0, ch.availableForRead)
+ ch.flush()
+ assertEquals(2, ch.availableForRead)
+ assertEquals(-1, ch.readShort())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testShortEdge() {
+ runBlocking {
+ ch.writeByte(1)
+
+ for (i in 2 until Size step 2) {
+ ch.writeShort(0x00ee)
+ }
+
+ ch.flush()
+
+ ch.readByte()
+ ch.writeShort(0x1234)
+
+ ch.flush()
+
+ while (ch.availableForRead > 2) {
+ ch.readShort()
+ }
+
+ assertEquals(0x1234, ch.readShort())
+ }
+ }
+
+ @Test
+ fun testIntB() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.BIG_ENDIAN
+ ch.writeByteOrder = ByteOrder.BIG_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeInt(-1)
+ ch.flush()
+ assertEquals(4, ch.availableForRead)
+ assertEquals(-1, ch.readInt())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testIntL() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.LITTLE_ENDIAN
+ ch.writeByteOrder = ByteOrder.LITTLE_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeInt(-1)
+ ch.flush()
+ assertEquals(4, ch.availableForRead)
+ assertEquals(-1, ch.readInt())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testIntEdge() {
+ runBlocking {
+ for (shift in 1..3) {
+ for (i in 1..shift) {
+ ch.writeByte(1)
+ }
+
+ repeat(Size / 4 - 1) {
+ ch.writeInt(0xeeeeeeeeL)
+ }
+
+ ch.flush()
+
+ for (i in 1..shift) {
+ ch.readByte()
+ }
+
+ ch.writeInt(0x12345678)
+
+ ch.flush()
+
+ while (ch.availableForRead > 4) {
+ ch.readInt()
+ }
+
+ assertEquals(0x12345678, ch.readInt())
+ }
+ }
+ }
+
+ @Test
+ fun testLongB() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.BIG_ENDIAN
+ ch.writeByteOrder = ByteOrder.BIG_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeLong(Long.MIN_VALUE)
+ ch.flush()
+ assertEquals(8, ch.availableForRead)
+ assertEquals(Long.MIN_VALUE, ch.readLong())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testLongL() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.LITTLE_ENDIAN
+ ch.writeByteOrder = ByteOrder.LITTLE_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeLong(Long.MIN_VALUE)
+ ch.flush()
+ assertEquals(8, ch.availableForRead)
+ assertEquals(Long.MIN_VALUE, ch.readLong())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testLongEdge() {
+ runBlocking {
+ for (shift in 1..7) {
+ for (i in 1..shift) {
+ ch.writeByte(1)
+ }
+
+ repeat(Size / 8 - 1) {
+ ch.writeLong(0x11112222eeeeeeeeL)
+ }
+
+ ch.flush()
+ for (i in 1..shift) {
+ ch.readByte()
+ }
+
+ ch.writeLong(0x1234567812345678L)
+ ch.flush()
+
+ while (ch.availableForRead > 8) {
+ ch.readLong()
+ }
+
+ assertEquals(0x1234567812345678L, ch.readLong())
+ }
+ }
+ }
+
+ @Test
+ fun testDoubleB() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.BIG_ENDIAN
+ ch.writeByteOrder = ByteOrder.BIG_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeDouble(1.05)
+ ch.flush()
+
+ assertEquals(8, ch.availableForRead)
+ assertEquals(1.05, ch.readDouble())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testDoubleL() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.LITTLE_ENDIAN
+ ch.writeByteOrder = ByteOrder.LITTLE_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeDouble(1.05)
+ ch.flush()
+
+ assertEquals(8, ch.availableForRead)
+ assertEquals(1.05, ch.readDouble())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testFloatB() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.BIG_ENDIAN
+ ch.writeByteOrder = ByteOrder.BIG_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeFloat(1.05f)
+ ch.flush()
+
+ assertEquals(4, ch.availableForRead)
+ assertEquals(1.05f, ch.readFloat())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+ @Test
+ fun testFloatL() {
+ runBlocking {
+ ch.readByteOrder = ByteOrder.LITTLE_ENDIAN
+ ch.writeByteOrder = ByteOrder.LITTLE_ENDIAN
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeFloat(1.05f)
+ ch.flush()
+
+ assertEquals(4, ch.availableForRead)
+ assertEquals(1.05f, ch.readFloat())
+ assertEquals(0, ch.availableForRead)
+ }
+ }
+
+
+
+ @Test
+ fun testEndianMix() {
+ val byteOrders = listOf(ByteOrder.BIG_ENDIAN, ByteOrder.LITTLE_ENDIAN)
+ runBlocking {
+ for (writeOrder in byteOrders) {
+ ch.writeByteOrder = writeOrder
+
+ for (readOrder in byteOrders) {
+ ch.readByteOrder = readOrder
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeShort(0x001f)
+ ch.flush()
+ if (writeOrder == readOrder)
+ assertEquals(0x001f, ch.readShort())
+ else
+ assertEquals(0x1f00, ch.readShort())
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeShort(0x001f)
+ ch.flush()
+ if (writeOrder == readOrder)
+ assertEquals(0x001f, ch.readShort())
+ else
+ assertEquals(0x1f00, ch.readShort())
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeInt(0x1f)
+ ch.flush()
+ if (writeOrder == readOrder)
+ assertEquals(0x0000001f, ch.readInt())
+ else
+ assertEquals(0x1f000000, ch.readInt())
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeInt(0x1fL)
+ ch.flush()
+ if (writeOrder == readOrder)
+ assertEquals(0x0000001f, ch.readInt())
+ else
+ assertEquals(0x1f000000, ch.readInt())
+
+ assertEquals(0, ch.availableForRead)
+ ch.writeLong(0x1f)
+ ch.flush()
+ if (writeOrder == readOrder)
+ assertEquals(0x1f, ch.readLong())
+ else
+ assertEquals(0x1f00000000000000L, ch.readLong())
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testClose() {
+ runBlocking {
+ ch.writeByte(1)
+ ch.writeByte(2)
+ ch.writeByte(3)
+
+ ch.flush()
+ assertEquals(1, ch.readByte())
+ ch.close()
+
+ assertEquals(2, ch.readByte())
+ assertEquals(3, ch.readByte())
+
+ try {
+ ch.readByte()
+ fail()
+ } catch (expected: ClosedReceiveChannelException) {
+ }
+ }
+ }
+
+ @Test
+ fun testReadAndWriteFully() {
+ runBlocking {
+ val bytes = byteArrayOf(1, 2, 3, 4, 5)
+ val dst = ByteArray(5)
+
+ ch.writeFully(bytes)
+ ch.flush()
+ assertEquals(5, ch.availableForRead)
+ ch.readFully(dst)
+ assertTrue { dst.contentEquals(bytes) }
+
+ ch.writeFully(bytes)
+ ch.flush()
+
+ val dst2 = ByteArray(4)
+ ch.readFully(dst2)
+
+ assertEquals(1, ch.availableForRead)
+ assertEquals(5, ch.readByte())
+
+ ch.close()
+
+ try {
+ ch.readFully(dst)
+ fail("")
+ } catch (expected: ClosedReceiveChannelException) {
+ }
+ }
+ }
+
+ @Test
+ fun testReadAndWriteFullyByteBuffer() {
+ runBlocking {
+ val bytes = byteArrayOf(1, 2, 3, 4, 5)
+ val dst = ByteArray(5)
+
+ ch.writeFully(ByteBuffer.wrap(bytes))
+ ch.flush()
+ assertEquals(5, ch.availableForRead)
+ ch.readFully(ByteBuffer.wrap(dst))
+ assertTrue { dst.contentEquals(bytes) }
+
+ ch.writeFully(ByteBuffer.wrap(bytes))
+ ch.flush()
+
+ val dst2 = ByteArray(4)
+ ch.readFully(ByteBuffer.wrap(dst2))
+
+ assertEquals(1, ch.availableForRead)
+ assertEquals(5, ch.readByte())
+
+ ch.close()
+
+ try {
+ ch.readFully(ByteBuffer.wrap(dst))
+ fail("")
+ } catch (expected: ClosedReceiveChannelException) {
+ }
+ }
+ }
+
+ @Test
+ fun testReadAndWritePartially() {
+ runBlocking {
+ val bytes = byteArrayOf(1, 2, 3, 4, 5)
+
+ assertEquals(5, ch.writeAvailable(bytes))
+ ch.flush()
+ assertEquals(5, ch.readAvailable(ByteArray(100)))
+
+ repeat(Size / bytes.size) {
+ assertNotEquals(0, ch.writeAvailable(bytes))
+ ch.flush()
+ }
+
+ ch.readAvailable(ByteArray(ch.availableForRead - 1))
+ assertEquals(1, ch.readAvailable(ByteArray(100)))
+
+ ch.close()
+ }
+ }
+
+ @Test
+ fun testReadAndWritePartiallyByteBuffer() {
+ runBlocking {
+ val bytes = byteArrayOf(1, 2, 3, 4, 5)
+
+ assertEquals(5, ch.writeAvailable(ByteBuffer.wrap(bytes)))
+ ch.flush()
+ assertEquals(5, ch.readAvailable(ByteBuffer.allocate(100)))
+
+ repeat(Size / bytes.size) {
+ assertNotEquals(0, ch.writeAvailable(ByteBuffer.wrap(bytes)))
+ ch.flush()
+ }
+
+ ch.readAvailable(ByteArray(ch.availableForRead - 1))
+ assertEquals(1, ch.readAvailable(ByteBuffer.allocate(100)))
+
+ ch.close()
+ }
+ }
+
+
+ @Test
+ fun testReadAndWriteBig() {
+ val count = 200
+ val bytes = ByteArray(65536)
+ Random().nextBytes(bytes)
+
+ launch(CommonPool + CoroutineName("writer")) {
+ for (i in 1..count) {
+ ch.writeFully(bytes)
+ ch.flush()
+ }
+ }.invokeOnCompletion { t ->
+ if (t != null) {
+ failures.addError(t)
+ }
+ }
+
+ runBlocking(CoroutineName("reader")) {
+ val dst = ByteArray(bytes.size)
+ for (i in 1..count) {
+ ch.readFully(dst)
+ assertTrue { dst.contentEquals(bytes) }
+ dst.fill(0)
+ }
+ }
+ }
+
+ @Test
+ fun testReadAndWriteBigByteBuffer() {
+ val count = 200
+ val bytes = ByteArray(65536)
+ Random().nextBytes(bytes)
+
+ launch(CommonPool + CoroutineName("writer")) {
+ for (i in 1..count) {
+ ch.writeFully(ByteBuffer.wrap(bytes))
+ ch.flush()
+ }
+ }.invokeOnCompletion { t ->
+ if (t != null) {
+ failures.addError(t)
+ }
+ }
+
+ runBlocking(CoroutineName("reader")) {
+ val dst = ByteArray(bytes.size)
+ for (i in 1..count) {
+ ch.readFully(ByteBuffer.wrap(dst))
+ assertTrue { dst.contentEquals(bytes) }
+ dst.fill(0)
+ }
+ }
+ }
+
+ @Test
+ fun testPacket() = runBlocking {
+ val packet = buildPacket {
+ writeInt(0xffee)
+ writeStringUtf8("Hello")
+ }
+
+ ch.writeInt(packet.remaining)
+ ch.writePacket(packet)
+
+ ch.flush()
+
+ val size = ch.readInt()
+ val readed = ch.readPacket(size)
+
+ assertEquals(0xffee, readed.readInt())
+ assertEquals("Hello", readed.readUTF8Line())
+ }
+
+ @Test
+ fun testBigPacket() = runBlocking {
+ launch(CommonPool + CoroutineName("writer")) {
+ val packet = buildPacket {
+ writeInt(0xffee)
+ writeStringUtf8(".".repeat(8192))
+ }
+
+ ch.writeInt(packet.remaining)
+ ch.writePacket(packet)
+
+ ch.flush()
+ }
+
+ val size = ch.readInt()
+ val readed = ch.readPacket(size)
+
+ assertEquals(0xffee, readed.readInt())
+ assertEquals(".".repeat(8192), readed.readUTF8Line())
+ }
+
+ @Test
+ fun testWriteString() = runBlocking {
+ ch.writeStringUtf8("abc")
+ ch.close()
+
+ assertEquals("abc", ch.readASCIILine())
+ }
+
+ @Test
+ fun testWriteCharSequence() = runBlocking {
+ ch.writeStringUtf8("abc" as CharSequence)
+ ch.close()
+
+ assertEquals("abc", ch.readASCIILine())
+ }
+
+ @Test
+ fun testWriteCharBuffer() = runBlocking {
+ val cb = CharBuffer.allocate(6)
+
+ for (i in 0 until cb.remaining()) {
+ cb.put(i, ' ')
+ }
+
+ cb.position(2)
+ cb.put(2, 'a')
+ cb.put(3, 'b')
+ cb.put(4, 'c')
+ cb.limit(5)
+
+ assertEquals("abc", cb.slice().toString())
+
+ ch.writeStringUtf8(cb)
+ ch.close()
+
+ assertEquals("abc", ch.readASCIILine())
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/BytePacketBuildTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/BytePacketBuildTest.kt
new file mode 100644
index 0000000..ef028ba
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/BytePacketBuildTest.kt
@@ -0,0 +1,162 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.io.packet.*
+import org.junit.Test
+import java.io.*
+import java.nio.ByteBuffer
+import kotlin.test.*
+
+class BytePacketBuildTest {
+ @Test
+ fun smokeSingleBufferTest() {
+ val p = buildPacket {
+ writeFully(ByteArray(2))
+ writeFully(ByteBuffer.allocate(3))
+
+ writeByte(0x12)
+ writeShort(0x1234)
+ writeInt(0x12345678)
+ writeDouble(1.23)
+ writeFloat(1.23f)
+ writeLong(0x123456789abcdef0)
+
+ writeStringUtf8("OK\n")
+ listOf(1, 2, 3).joinTo(this, separator = "|")
+ }
+
+ assertEquals(2 + 3 + 1 + 2 + 4 + 8 + 4 + 8 + 3 + 5, p.remaining)
+
+ p.readFully(ByteArray(2))
+ p.readFully(ByteBuffer.allocate(3))
+
+ assertEquals(0x12, p.readByte())
+ assertEquals(0x1234, p.readShort())
+ assertEquals(0x12345678, p.readInt())
+ assertEquals(1.23, p.readDouble())
+ assertEquals(1.23f, p.readFloat())
+ assertEquals(0x123456789abcdef0, p.readLong())
+
+ assertEquals("OK", p.readUTF8Line())
+ assertEquals("1|2|3", p.readUTF8Line())
+ }
+
+ @Test
+ fun smokeMultiBufferTest() {
+ val p = buildPacket {
+ writeFully(ByteArray(9999))
+ writeFully(ByteBuffer.allocate(8888))
+ writeByte(0x12)
+ writeShort(0x1234)
+ writeInt(0x12345678)
+ writeDouble(1.23)
+ writeFloat(1.23f)
+ writeLong(0x123456789abcdef0)
+
+ writeStringUtf8("OK\n")
+ listOf(1, 2, 3).joinTo(this, separator = "|")
+ }
+
+ assertEquals(9999 + 8888 + 1 + 2 + 4 + 8 + 4 + 8 + 3 + 5, p.remaining)
+
+ p.readFully(ByteArray(9999))
+ p.readFully(ByteBuffer.allocate(8888))
+ assertEquals(0x12, p.readByte())
+ assertEquals(0x1234, p.readShort())
+ assertEquals(0x12345678, p.readInt())
+ assertEquals(1.23, p.readDouble())
+ assertEquals(1.23f, p.readFloat())
+ assertEquals(0x123456789abcdef0, p.readLong())
+
+ assertEquals("OK", p.readUTF8Line())
+ assertEquals("1|2|3", p.readUTF8Line())
+ }
+
+ @Test
+ fun testSingleBufferSkipTooMuch() {
+ val p = buildPacket {
+ writeFully(ByteArray(9999))
+ }
+
+ assertEquals(9999, p.skip(10000))
+ }
+
+ @Test
+ fun testSingleBufferSkip() {
+ val p = buildPacket {
+ writeFully("ABC123".toByteArray())
+ }
+
+ assertEquals(3, p.skip(3))
+ assertEquals("123", p.readUTF8Line())
+ }
+
+ @Test
+ fun testSingleBufferSkipExact() {
+ val p = buildPacket {
+ writeFully("ABC123".toByteArray())
+ }
+
+ p.skipExact(3)
+ assertEquals("123", p.readUTF8Line())
+ }
+
+ @Test(expected = EOFException::class)
+ fun testSingleBufferSkipExactTooMuch() {
+ val p = buildPacket {
+ writeFully("ABC123".toByteArray())
+ }
+
+ p.skipExact(1000)
+ }
+
+ @Test
+ fun testMultiBufferSkipTooMuch() {
+ val p = buildPacket {
+ writeFully(ByteArray(99999))
+ }
+
+ assertEquals(99999, p.skip(1000000))
+ }
+
+ @Test
+ fun testMultiBufferSkip() {
+ val p = buildPacket {
+ writeFully(ByteArray(99999))
+ writeFully("ABC123".toByteArray())
+ }
+
+ assertEquals(99999 + 3, p.skip(99999 + 3))
+ assertEquals("123", p.readUTF8Line())
+ }
+
+ @Test
+ fun testNextBufferBytesStealing() {
+ val p = buildPacket {
+ repeat(PACKET_BUFFER_SIZE + 3) {
+ writeByte(1)
+ }
+ }
+
+ assertEquals(PACKET_BUFFER_SIZE + 3, p.remaining)
+ p.readFully(ByteArray(PACKET_BUFFER_SIZE - 1))
+ assertEquals(0x01010101, p.readInt())
+ }
+
+ @Test
+ fun testNextBufferBytesStealingFailed() {
+ val p = buildPacket {
+ repeat(PACKET_BUFFER_SIZE + 1) {
+ writeByte(1)
+ }
+ }
+
+ p.readFully(ByteArray(PACKET_BUFFER_SIZE - 1))
+
+ try {
+ p.readInt()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/BytePacketReaderWriterTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/BytePacketReaderWriterTest.kt
new file mode 100644
index 0000000..d039fcc
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/BytePacketReaderWriterTest.kt
@@ -0,0 +1,380 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.io.packet.*
+import org.junit.*
+import java.util.*
+import kotlin.test.*
+
+class BytePacketReaderWriterTest {
+ @Test
+ fun testReaderEmpty() {
+ val packet = buildPacket {
+ }
+
+ assertEquals(-1, packet.readerUTF8().read())
+ }
+
+ @Test
+ fun testReaderFew() {
+ val packet = buildPacket {
+ append("ABC")
+ }
+
+ assertEquals("ABC", packet.readerUTF8().readText())
+ }
+
+ @Test
+ fun testReaderMultiple() {
+ val s = buildString {
+ repeat(100000) {
+ append("e")
+ }
+ }
+
+ val packet = buildPacket {
+ append(s)
+ }
+
+ assertEquals(s, packet.readerUTF8().readText())
+ }
+
+ @Test
+ fun testReaderFewUtf() {
+ val s = "\u0447"
+ val packet = buildPacket {
+ append(s)
+ }
+
+ assertEquals(s, packet.readerUTF8().readText())
+ }
+
+ @Test
+ fun testReaderFewUtf3bytes() {
+ val s = "\u0BF5"
+ val packet = buildPacket {
+ append(s)
+ }
+
+ assertEquals(s, packet.readerUTF8().readText())
+ }
+
+ @Test
+ fun testReaderMultipleUtf() {
+ val s = buildString {
+ repeat(100000) {
+ append("\u0447")
+ }
+ }
+
+ val packet = buildPacket {
+ append(s)
+ }
+
+ assertEquals(s, packet.readerUTF8().readText())
+ }
+
+ @Test
+ fun testReaderMultipleUtf3bytes() {
+ val s = buildString {
+ repeat(100000) {
+ append("\u0BF5")
+ }
+ }
+
+ val packet = buildPacket {
+ append(s)
+ }
+
+ assertEquals(s, packet.readerUTF8().readText())
+ }
+
+ @Test
+ fun testWriterSingleBufferSingleWrite() {
+ val s = buildString {
+ append("ABC")
+ }
+
+ val packet = buildPacket {
+ writerUTF8().write(s)
+ }
+
+ assertEquals(s, packet.inputStream().readBytes().toString(Charsets.UTF_8))
+ }
+
+ @Test
+ fun testWriterSingleBufferSingleWriteUtf() {
+ val s = buildString {
+ append("A\u0447C")
+ }
+
+ val packet = buildPacket {
+ writerUTF8().write(s)
+ }
+
+ assertEquals(s, packet.inputStream().readBytes().toString(Charsets.UTF_8))
+ }
+
+ @Test
+ fun testWriterSingleBufferMultipleWrite() {
+ val s = buildString {
+ append("ABC")
+ }
+
+ val packet = buildPacket {
+ writerUTF8().apply {
+ write(s.substring(0, 1))
+ write(s.substring(1))
+ }
+ }
+
+ assertEquals(s, packet.inputStream().readBytes().toString(Charsets.UTF_8))
+ }
+
+ @Test
+ fun testWriterSingleBufferMultipleWriteUtf() {
+ val s = buildString {
+ append("\u0447BC")
+ append("A\u0447C")
+ append("AB\u0447")
+ append("\u0447")
+ }
+
+ val packet = buildPacket {
+ writerUTF8().let { w ->
+ w.write("\u0447BC")
+ w.write("A\u0447C")
+ w.write("AB\u0447")
+ w.write("\u0447")
+ }
+ }
+
+ assertEquals(s, packet.inputStream().readBytes().toString(Charsets.UTF_8))
+ }
+
+ @Test
+ fun testWriterMultiBufferSingleWrite() {
+ val s = buildString {
+ repeat(100000) {
+ append("x")
+ }
+ }
+
+ val packet = buildPacket {
+ writerUTF8().write(s)
+ }
+
+ assertEquals(s, packet.inputStream().readBytes().toString(Charsets.UTF_8))
+ }
+
+ @Test
+ fun testWriterMultiBufferSingleWriteUtf() {
+ val s = buildString {
+ repeat(100000) {
+ append("A\u0447")
+ }
+ }
+
+ val packet = buildPacket {
+ writerUTF8().write(s)
+ }
+
+ assertEquals(s, packet.inputStream().readBytes().toString(Charsets.UTF_8))
+ }
+
+ @Test
+ fun testWriterMultiBufferSingleWriteUtf3bytes() {
+ val s = buildString {
+ repeat(100000) {
+ append("\u0BF5")
+ }
+ }
+
+ val packet = buildPacket {
+ writerUTF8().write(s)
+ }
+
+ assertEquals(s, packet.inputStream().readBytes().toString(Charsets.UTF_8))
+ }
+
+ @Test
+ fun testReadLineSingleBuffer() {
+ val p = buildPacket {
+ append("1\r22\n333\r\n4444")
+ }
+
+ assertEquals("1", p.readUTF8Line())
+ assertEquals("22", p.readUTF8Line())
+ assertEquals("333", p.readUTF8Line())
+ assertEquals("4444", p.readUTF8Line())
+ assertNull(p.readUTF8Line())
+ }
+
+ @Test
+ fun testReadLineMutiBuffer() {
+ val p = buildPacket {
+ repeat(1000) {
+ append("1\r22\n333\r\n4444\n")
+ }
+ }
+
+ repeat(1000) {
+ assertEquals("1", p.readUTF8Line())
+ assertEquals("22", p.readUTF8Line())
+ assertEquals("333", p.readUTF8Line())
+ assertEquals("4444", p.readUTF8Line())
+ }
+
+ assertNull(p.readUTF8Line())
+ }
+
+ @Test
+ fun testSingleBufferReadText() {
+ val p = buildPacket {
+ append("ABC")
+ }
+
+ assertEquals("ABC", p.readText().toString())
+ }
+
+ @Test
+ fun testMultiBufferReadText() {
+ val s = buildString {
+ repeat(100000) {
+ append("x")
+ }
+ }
+
+ val packet = buildPacket {
+ writeFully(s.toByteArray())
+ }
+
+ assertEquals(s, packet.readText().toString())
+ }
+
+ @Test
+ fun testSingleBufferReadAll() {
+ val bb = ByteArray(100)
+ Random().nextBytes(bb)
+
+ val p = buildPacket {
+ writeFully(bb)
+ }
+
+ assertTrue { bb.contentEquals(p.readBytes()) }
+ }
+
+ @Test
+ fun testMultiBufferReadAll() {
+ val bb = ByteArray(100000)
+ Random().nextBytes(bb)
+
+ val p = buildPacket {
+ writeFully(bb)
+ }
+
+ assertTrue { bb.contentEquals(p.readBytes()) }
+ }
+
+ @Test
+ fun testCopySingleBufferPacket() {
+ val bb = ByteArray(100)
+ Random().nextBytes(bb)
+
+ val p = buildPacket {
+ writeFully(bb)
+ }
+
+ val copy = p.copy()
+ assertEquals(p.remaining, p.remaining)
+ assertTrue { p.readBytes().contentEquals(copy.readBytes()) }
+ }
+
+ @Test
+ fun testCopyMultipleBufferPacket() {
+ val bb = ByteArray(1000000)
+ Random().nextBytes(bb)
+
+ val p = buildPacket {
+ writeFully(bb)
+ }
+
+ val copy = p.copy()
+ assertEquals(p.remaining, p.remaining)
+ val bytes = p.readBytes()
+ val copied = copy.readBytes()
+
+ assertTrue { bytes.contentEquals(copied) }
+ }
+
+ @Test
+ fun testWritePacketSingle() {
+ val inner = buildPacket {
+ append("ABC")
+ }
+
+ val outer = buildPacket {
+ append("123")
+ assertEquals(3, size)
+ writePacket(inner)
+ assertEquals(6, size)
+ append(".")
+ }
+
+ assertEquals("123ABC.", outer.readText().toString())
+ assertEquals(0, inner.remaining)
+ }
+
+ @Test
+ fun testWritePacketMultiple() {
+ val inner = buildPacket {
+ append("o".repeat(100000))
+ }
+
+ val outer = buildPacket {
+ append("123")
+ assertEquals(3, size)
+ writePacket(inner)
+ assertEquals(100003, size)
+ append(".")
+ }
+
+ assertEquals("123" + "o".repeat(100000) + ".", outer.readText().toString())
+ assertEquals(0, inner.remaining)
+ }
+
+ @Test
+ fun testWritePacketSingleUnconsumed() {
+ val inner = buildPacket {
+ append("ABC")
+ }
+
+ val outer = buildPacket {
+ append("123")
+ assertEquals(3, size)
+ writePacketUnconsumed(inner)
+ assertEquals(6, size)
+ append(".")
+ }
+
+ assertEquals("123ABC.", outer.readText().toString())
+ assertEquals(3, inner.remaining)
+ }
+
+ @Test
+ fun testWritePacketMultipleUnconsumed() {
+ val inner = buildPacket {
+ append("o".repeat(100000))
+ }
+
+ val outer = buildPacket {
+ append("123")
+ assertEquals(3, size)
+ writePacketUnconsumed(inner)
+ assertEquals(100003, size)
+ append(".")
+ }
+
+ assertEquals("123" + "o".repeat(100000) + ".", outer.readText().toString())
+ assertEquals(100000, inner.remaining)
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ContentByteBufferTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ContentByteBufferTest.kt
new file mode 100644
index 0000000..2b9547e
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ContentByteBufferTest.kt
@@ -0,0 +1,53 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.runBlocking
+import org.junit.Test
+import java.util.*
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class ContentByteBufferTest {
+ @Test
+ fun testEmptyContent() = runBlocking {
+ val ch = ByteReadChannel(ByteArray(0))
+ assertEquals(0, ch.availableForRead)
+ assertEquals(-1, ch.readAvailable(ByteBuffer.allocate(100)))
+ assertTrue { ch.isClosedForRead }
+ }
+
+ @Test
+ fun testSingleByteContent() = runBlocking {
+ val ch = ByteReadChannel(byteArrayOf(1))
+ assertEquals(1, ch.availableForRead)
+ assertFalse { ch.isClosedForRead }
+ assertEquals(1, ch.readAvailable(ByteBuffer.allocate(100)))
+ assertEquals(0, ch.availableForRead)
+ assertTrue { ch.isClosedForRead }
+ }
+
+ @Test
+ fun testSingleByteContent2() = runBlocking {
+ val ch = ByteReadChannel(byteArrayOf(0x34))
+ assertEquals(1, ch.availableForRead)
+ assertFalse { ch.isClosedForRead }
+ assertEquals(0x34, ch.readByte())
+ assertEquals(0, ch.availableForRead)
+ assertTrue { ch.isClosedForRead }
+ }
+
+ @Test
+ fun testMultipleByteContent2() = runBlocking {
+ val arr = ByteArray(16)
+ Random().nextBytes(arr)
+ val ch = ByteReadChannel(arr)
+ assertEquals(16, ch.availableForRead)
+ assertFalse { ch.isClosedForRead }
+ ch.readByte()
+ ch.readShort()
+ ch.readInt()
+ ch.readLong()
+ ch.readByte()
+ assertTrue { ch.isClosedForRead }
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/CopyAndCloseTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/CopyAndCloseTest.kt
new file mode 100644
index 0000000..ddeae14
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/CopyAndCloseTest.kt
@@ -0,0 +1,153 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.io.internal.*
+import org.junit.*
+import java.io.*
+import kotlin.test.*
+
+class CopyAndCloseTest : TestBase() {
+ private val from = ByteChannel(true)
+ private val to = ByteChannel(true)
+
+ @After
+ fun tearDown() {
+ from.close(CancellationException())
+ to.close(CancellationException())
+ }
+
+ @Test
+ fun smokeTest() = runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+ val copied = from.copyAndClose(to) // should suspend
+
+ expect(7)
+
+ assertEquals(8, copied)
+ }
+
+ yield()
+
+ expect(3)
+ from.writeInt(1)
+ expect(4)
+ from.writeInt(2)
+ expect(5)
+
+ yield()
+ expect(6)
+
+ from.close()
+ yield()
+
+ finish(8)
+ }
+
+ @Test
+ fun failurePropagation() = runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+
+ try {
+ from.copyAndClose(to) // should suspend and then throw IOException
+ fail("Should rethrow exception")
+ } catch (expected: IOException) {
+ }
+
+ expect(4)
+ }
+
+ yield()
+ expect(3)
+
+ from.close(IOException())
+ yield()
+
+ expect(5)
+
+ try {
+ to.readInt()
+ fail("Should throw exception")
+ } catch (expected: IOException) {
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun copyLimitedTest() = runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+ val copied = from.copyTo(to, 3) // should suspend
+
+ expect(5)
+
+ assertEquals(3, copied)
+ }
+
+ yield()
+
+ expect(3)
+ from.writeByte(1)
+ yield()
+
+ expect(4)
+ from.writeInt(2)
+ yield()
+
+ finish(6)
+ }
+
+ @Test
+ fun readRemaining() = runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+ from.writeFully("123".toByteArray())
+
+ yield()
+ expect(3)
+ from.writeFully("456".toByteArray().asByteBuffer())
+
+ yield()
+ expect(4)
+ from.close()
+ }
+
+ yield()
+ assertEquals("123456", from.readRemaining().readText().toString())
+
+ yield()
+
+ finish(5)
+ }
+
+ @Test
+ fun readRemainingLimitFailed() = runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+ from.writeFully("123".toByteArray())
+
+ yield()
+ expect(3)
+ from.writeFully("456".toByteArray().asByteBuffer())
+ }
+
+ yield()
+ assertEquals("12345", from.readRemaining(5).readText().toString())
+
+ finish(4)
+ }
+
+
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/PooledBufferTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/PooledBufferTest.kt
new file mode 100644
index 0000000..33b77e6
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/PooledBufferTest.kt
@@ -0,0 +1,96 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.io.internal.ObjectPool
+import kotlinx.coroutines.experimental.io.internal.ReadWriteBufferState
+import kotlinx.coroutines.experimental.runBlocking
+import org.junit.After
+import org.junit.Test
+import java.io.IOException
+import java.nio.ByteBuffer
+import java.util.concurrent.CopyOnWriteArrayList
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+class PooledBufferTest {
+ private val allocated = CopyOnWriteArrayList<ByteBuffer>()
+
+ private inner class TestPool : ObjectPool<ReadWriteBufferState.Initial> {
+ override val capacity: Int get() = 0
+
+ override fun borrow(): ReadWriteBufferState.Initial {
+ val buffer = ReadWriteBufferState.Initial(ByteBuffer.allocate(4096))
+ allocated.add(buffer.backingBuffer)
+ return buffer
+ }
+
+ override fun recycle(instance: ReadWriteBufferState.Initial) {
+ if (!allocated.remove(instance.backingBuffer)) {
+ fail("Couldn't release buffer from pool")
+ }
+ }
+
+ override fun dispose() {
+ }
+ }
+
+ private val channel = ByteBufferChannel(autoFlush = true, pool = TestPool())
+
+ @After
+ fun tearDown() {
+ assertTrue { allocated.isEmpty() }
+ }
+
+ @Test
+ fun testWriteReadClose() {
+ runBlocking {
+ channel.writeInt(1)
+ assertEquals(1, allocated.size)
+ channel.readInt()
+ channel.close()
+ assertEquals(0, allocated.size)
+ }
+ }
+
+ @Test
+ fun testWriteCloseRead() {
+ runBlocking {
+ channel.writeInt(1)
+ assertEquals(1, allocated.size)
+ channel.close()
+ channel.readInt()
+ assertEquals(0, allocated.size)
+ }
+ }
+
+ @Test
+ fun testWriteCloseReadRead() {
+ runBlocking {
+ channel.writeInt(1)
+ assertEquals(1, allocated.size)
+ channel.close()
+ channel.readShort()
+ assertEquals(1, allocated.size)
+ channel.readShort()
+ assertEquals(0, allocated.size)
+ }
+ }
+
+ @Test
+ fun testCloseOnly() {
+ runBlocking {
+ channel.close()
+ assertEquals(0, allocated.size)
+ }
+ }
+
+ @Test
+ fun testCloseWithEerror() {
+ runBlocking {
+ channel.writeFully("OK".toByteArray())
+ assertEquals(1, allocated.size)
+ channel.close(IOException())
+ assertEquals(0, allocated.size)
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ReadUntilDelimiterTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ReadUntilDelimiterTest.kt
new file mode 100644
index 0000000..039482e
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ReadUntilDelimiterTest.kt
@@ -0,0 +1,474 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.io.internal.*
+import org.junit.*
+import java.io.*
+import kotlin.test.*
+
+class ReadUntilDelimiterTest : TestBase() {
+ private val source = ByteChannel(true)
+ private val nonRepeatingDelimiter = "123".toByteArray().asByteBuffer()
+ private val repeatingDelimiter = "AAA".toByteArray().asByteBuffer()
+
+ @Before
+ fun setUp() {
+ nonRepeatingDelimiter.clear()
+ repeatingDelimiter.clear()
+
+// Thread.sleep(5000)
+ }
+
+ @After
+ fun tearDown() {
+ source.close(CancellationException())
+ }
+
+ @Test
+ fun testReadUntilDelimiterOnClosed() = runBlocking {
+ source.close()
+ assertEquals(-1, source.readUntilDelimiter(nonRepeatingDelimiter, ByteBuffer.allocate(100)))
+ }
+
+ @Test
+ fun testReadUntilDelimiterOnEmptyThenClose() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+ source.close()
+ }
+
+ expect(1)
+ assertEquals(-1, source.readUntilDelimiter(nonRepeatingDelimiter, ByteBuffer.allocate(100)))
+ finish(3)
+ }
+
+ @Test
+ fun smokeTest() = runBlocking {
+ val tmp = ByteBuffer.allocate(100)
+
+ source.writeInt(777)
+ source.writeFully(nonRepeatingDelimiter.duplicate())
+ source.writeInt(999)
+
+ val rc = source.readUntilDelimiter(nonRepeatingDelimiter, tmp)
+ assertEquals(4, rc)
+ tmp.flip()
+ assertEquals(4, tmp.remaining())
+ assertEquals(777, tmp.getInt())
+ assertEquals(0, nonRepeatingDelimiter.position())
+ assertEquals(3, nonRepeatingDelimiter.limit())
+
+ tmp.clear()
+ tmp.limit(nonRepeatingDelimiter.remaining())
+ source.readFully(tmp)
+
+ source.close()
+
+ tmp.clear()
+ val rc2 = source.readUntilDelimiter(nonRepeatingDelimiter, tmp)
+ assertEquals(4, rc2)
+ tmp.flip()
+ assertEquals(4, tmp.remaining())
+ assertEquals(999, tmp.getInt())
+ tmp.clear()
+
+ val rc3 = source.readUntilDelimiter(nonRepeatingDelimiter, tmp)
+ assertEquals(-1, rc3)
+ }
+
+ @Test
+ fun smokeTestWithRepeatingDelimiter() = runBlocking {
+ val tmp = ByteBuffer.allocate(100)
+
+ source.writeInt(777)
+ source.writeFully(repeatingDelimiter.duplicate())
+ source.writeInt(999)
+
+ val rc = source.readUntilDelimiter(repeatingDelimiter, tmp)
+ assertEquals(4, rc)
+ tmp.flip()
+ assertEquals(4, tmp.remaining())
+ assertEquals(777, tmp.getInt())
+ assertEquals(0, repeatingDelimiter.position())
+ assertEquals(3, repeatingDelimiter.limit())
+
+ tmp.clear()
+ tmp.limit(repeatingDelimiter.remaining())
+ source.readFully(tmp)
+
+ source.close()
+
+ tmp.clear()
+ val rc2 = source.readUntilDelimiter(repeatingDelimiter, tmp)
+ assertEquals(4, rc2)
+ tmp.flip()
+ assertEquals(4, tmp.remaining())
+ assertEquals(999, tmp.getInt())
+ tmp.clear()
+
+ val rc3 = source.readUntilDelimiter(repeatingDelimiter, tmp)
+ assertEquals(-1, rc3)
+ }
+
+ @Test
+ fun testEnsureSuspendOrder() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+ source.writeInt(777)
+ yield()
+ expect(3)
+ source.writeInt(999)
+ yield()
+ expect(4)
+ source.writeFully(nonRepeatingDelimiter.duplicate())
+ }
+
+ expect(1)
+ val tmp = ByteBuffer.allocate(100)
+ val rc = source.readUntilDelimiter(nonRepeatingDelimiter, tmp)
+ expect(5)
+
+ assertEquals(8, rc)
+ tmp.flip()
+ assertEquals(777, tmp.getInt())
+ assertEquals(999, tmp.getInt())
+ tmp.clear()
+
+ expect(6)
+
+ assertEquals(0, source.readUntilDelimiter(nonRepeatingDelimiter, tmp))
+
+ source.skipDelimiter(nonRepeatingDelimiter)
+
+ source.close()
+ assertEquals(-1, source.readUntilDelimiter(nonRepeatingDelimiter, tmp))
+
+
+ finish(7)
+ }
+
+ @Test
+ fun testBulkWrite() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+
+ val buffer = ByteBuffer.allocate(100)
+ buffer.putInt(777)
+ buffer.putInt(999)
+ buffer.put(nonRepeatingDelimiter.duplicate())
+ buffer.flip()
+
+ source.writeFully(buffer)
+ }
+
+ expect(1)
+ val tmp = ByteBuffer.allocate(100)
+ val rc = source.readUntilDelimiter(nonRepeatingDelimiter, tmp)
+ expect(3)
+
+ assertEquals(8, rc)
+ tmp.flip()
+ assertEquals(777, tmp.getInt())
+ assertEquals(999, tmp.getInt())
+ tmp.clear()
+
+ expect(4)
+
+ assertEquals(0, source.readUntilDelimiter(nonRepeatingDelimiter, tmp))
+
+ finish(5)
+ }
+
+ @Test
+ fun testPartitionedDelimiter() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+
+ val buffer = ByteBuffer.allocate(100)
+ buffer.putInt(777)
+ buffer.putInt(999)
+ buffer.put(nonRepeatingDelimiter.duplicate().apply { limit(1) })
+ buffer.flip()
+
+ source.writeFully(buffer)
+
+ yield()
+ expect(3)
+
+ source.writeFully(nonRepeatingDelimiter.duplicate().apply { position(1) })
+ source.close()
+ }
+
+ expect(1)
+ val tmp = ByteBuffer.allocate(100)
+ val rc = source.readUntilDelimiter(nonRepeatingDelimiter, tmp)
+ expect(4)
+
+ assertEquals(8, rc)
+ tmp.flip()
+ assertEquals(777, tmp.getInt())
+ assertEquals(999, tmp.getInt())
+ tmp.clear()
+
+ expect(5)
+
+ assertEquals(0, source.readUntilDelimiter(nonRepeatingDelimiter, tmp))
+ source.skipDelimiter(nonRepeatingDelimiter)
+ assertEquals(-1, source.readUntilDelimiter(nonRepeatingDelimiter, tmp))
+
+
+ finish(6)
+ }
+
+ @Test
+ fun testReadUntilDelimiterWrapped() = runBlocking {
+ val padSize = BUFFER_SIZE - 8 - 1
+
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(ByteBuffer.allocate(padSize - 1))
+ source.writeByte(99)
+ yield()
+
+ expect(4)
+ source.writeFully(nonRepeatingDelimiter.duplicate())
+ expect(5)
+ }
+
+ expect(1)
+ source.readFully(ByteBuffer.allocate(padSize - 1))
+ expect(3)
+
+ val tmp = ByteBuffer.allocate(100)
+ val rc = source.readUntilDelimiter(nonRepeatingDelimiter, tmp)
+ assertEquals(1, rc)
+ assertEquals(99, tmp.get(0).toInt())
+
+ finish(6)
+ }
+
+ @Test
+ fun testReadUntilDelimiterRepeatedWrapped() = runBlocking {
+ val padSize = BUFFER_SIZE - 8 - 1
+
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(ByteBuffer.allocate(padSize - 1))
+ source.writeByte(99)
+ yield()
+
+ expect(4)
+ source.writeFully(repeatingDelimiter.duplicate())
+ expect(5)
+ }
+
+ expect(1)
+ source.readFully(ByteBuffer.allocate(padSize - 1))
+ expect(3)
+
+ val tmp = ByteBuffer.allocate(100)
+ val rc = source.readUntilDelimiter(repeatingDelimiter, tmp)
+ assertEquals(1, rc)
+ assertEquals(99, tmp.get(0).toInt())
+
+ finish(6)
+ }
+
+ @Test
+ fun testReadUntilDelimiterPartialFailure() = runBlocking {
+ val padSize = BUFFER_SIZE - 8 - 1
+
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(ByteBuffer.allocate(padSize - 1))
+ source.writeByte(99)
+ yield()
+
+ expect(4)
+ source.writeByte(repeatingDelimiter.get(0))
+ source.writeByte(999)
+ expect(5)
+
+ yield()
+ expect(6)
+ source.close()
+ }
+
+ expect(1)
+ source.readFully(ByteBuffer.allocate(padSize - 1))
+ expect(3)
+
+ val tmp = ByteBuffer.allocate(100)
+ val rc = source.readUntilDelimiter(repeatingDelimiter, tmp)
+ expect(7)
+ assertEquals(3, rc)
+ assertEquals(99, tmp.get(0).toInt())
+
+ finish(8)
+ }
+
+ @Test
+ fun testReadUntilDelimiterPartialFailure2() = runBlocking {
+ val padSize = BUFFER_SIZE - 8 - 1
+
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(ByteBuffer.allocate(padSize - 1))
+ source.writeByte(99)
+ yield()
+
+ expect(4)
+ source.writeByte(repeatingDelimiter.get(0))
+ source.writeByte(88)
+ source.writeByte(77)
+ expect(5)
+
+ yield()
+ expect(6)
+ source.close()
+ }
+
+ expect(1)
+ source.readFully(ByteBuffer.allocate(padSize - 1))
+ expect(3)
+
+ val tmp = ByteBuffer.allocate(100)
+ val rc = source.readUntilDelimiter(repeatingDelimiter, tmp)
+ expect(7)
+ assertEquals(4, rc)
+ tmp.flip()
+ assertEquals(99, tmp.get().toInt())
+ assertEquals(repeatingDelimiter.get(0), tmp.get())
+ assertEquals(88, tmp.get().toInt())
+ assertEquals(77, tmp.get().toInt())
+
+ finish(8)
+ }
+
+ @Test
+ fun testReadUntilDelimiterWrappedNotEnoughThenFailure() = runBlocking {
+ val padSize = BUFFER_SIZE - 8 - 1
+
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(ByteBuffer.allocate(padSize - 1))
+ source.writeByte(99)
+ yield()
+
+ expect(4)
+ assertTrue { repeatingDelimiter.remaining() > 2 }
+ source.writeFully(repeatingDelimiter.duplicate().apply { limit(limit() - 1) })
+ expect(5)
+
+ yield()
+ expect(6)
+ source.close()
+ }
+
+ expect(1)
+ source.readFully(ByteBuffer.allocate(padSize - 1))
+ expect(3)
+
+ val tmp = ByteBuffer.allocate(100)
+ val rc = source.readUntilDelimiter(repeatingDelimiter, tmp)
+ expect(7)
+ assertEquals(3, rc)
+ tmp.flip()
+ assertEquals(99, tmp.get().toInt())
+ for (i in 0 until repeatingDelimiter.remaining() - 1) {
+ assertEquals(repeatingDelimiter.get(i), tmp.get())
+ }
+
+ finish(8)
+ }
+
+ @Test
+ fun testSkipDelimiterSuspend() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(nonRepeatingDelimiter.duplicate())
+ }
+
+ expect(1)
+ source.skipDelimiter(nonRepeatingDelimiter)
+ finish(3)
+ }
+
+ @Test
+ fun testSkipDelimiterFullyAvailable() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(nonRepeatingDelimiter.duplicate())
+ expect(3)
+ }
+
+ expect(1)
+ yield()
+ expect(4)
+ source.skipDelimiter(nonRepeatingDelimiter)
+ finish(5)
+ }
+
+ @Test
+ fun testSkipDelimiterSuspendMultiple() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(nonRepeatingDelimiter.duplicate().apply { limit(1) })
+ yield()
+ expect(3)
+ source.writeFully(nonRepeatingDelimiter.duplicate().apply { position(1) })
+ }
+
+ expect(1)
+ yield()
+ source.skipDelimiter(nonRepeatingDelimiter)
+ finish(4)
+ }
+
+ @Test
+ fun testSkipDelimiterSuspendRingBufferWrap() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+ source.writeFully(ByteBuffer.allocate(BUFFER_SIZE - 9))
+ yield()
+
+ expect(4)
+ source.writeFully(nonRepeatingDelimiter.duplicate())
+ yield()
+ }
+
+ expect(1)
+ source.readFully(ByteBuffer.allocate(BUFFER_SIZE - 9))
+ expect(3)
+
+ source.skipDelimiter(nonRepeatingDelimiter)
+ finish(5)
+ }
+
+ @Test
+ fun testSkipDelimiterBroken() = runBlocking {
+ launch(coroutineContext) {
+ expect(2)
+ val bb = ByteBuffer.allocate(nonRepeatingDelimiter.remaining())
+ bb.put(nonRepeatingDelimiter.duplicate())
+ bb.put(1, (bb.get(1) + 1).toByte())
+ bb.clear()
+ source.writeFully(bb)
+ expect(3)
+ }
+
+ expect(1)
+ yield()
+ expect(4)
+
+ try {
+ source.skipDelimiter(nonRepeatingDelimiter)
+ fail()
+ } catch (expected: IOException) {
+ }
+
+ finish(5)
+ }
+
+
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/StringScenarioTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/StringScenarioTest.kt
new file mode 100644
index 0000000..3ccd35e
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/StringScenarioTest.kt
@@ -0,0 +1,273 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.TestBase
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
+import kotlinx.coroutines.experimental.yield
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.Timeout
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class StringScenarioTest : TestBase() {
+ @get:Rule
+ val timeout = Timeout(10L, TimeUnit.SECONDS)
+
+ private val ch = ByteBufferChannel(autoFlush = true)
+
+ @Test
+ fun testWriteCharByChar() {
+ runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ ch.writeStringUtf8("A")
+ expect(3)
+ yield()
+
+ expect(5)
+ ch.writeStringUtf8("B")
+ expect(6)
+ yield()
+
+ expect(7)
+ ch.writeStringUtf8("\n")
+ expect(8)
+ yield()
+ }
+
+ expect(2)
+ yield()
+
+ expect(4)
+ val line = ch.readUTF8Line()
+
+ assertEquals("AB", line)
+ expect(9)
+
+ yield()
+ expect(10)
+ finish(11)
+ }
+ }
+
+ @Test
+ fun testSplitUtf8() {
+ runBlocking {
+ val sb = StringBuilder()
+
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+ val b = byteArrayOf(0xd0.toByte(), 0x9a.toByte(), 0x0a)
+ ch.writeFully(b, 0, 1)
+ yield()
+
+ expect(3)
+ assertTrue { sb.isEmpty() }
+
+ ch.writeFully(b, 1, 1)
+ yield()
+
+ expect(4)
+ assertEquals("\u041a", sb.toString())
+
+ ch.writeFully(b, 2, 1)
+ yield()
+ }
+
+ ch.readUTF8LineTo(sb)
+ expect(5)
+
+ assertEquals("\u041a", sb.toString())
+
+ finish(6)
+ }
+ }
+
+ @Test
+ fun testSplitLineDelimiter() = runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+ ch.writeFully("ABC\r".toByteArray())
+ expect(3)
+ yield()
+
+ expect(5)
+ ch.writeFully("\n".toByteArray())
+ yield()
+ }
+
+ yield()
+
+ expect(4)
+ val line = ch.readASCIILine()
+ expect(6)
+
+ assertEquals("ABC", line)
+
+ finish(7)
+ }
+
+ @Test
+ fun testReadTailWriteFirst() = runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+
+ ch.writeFully("ABC".toByteArray())
+
+ yield()
+
+ expect(4)
+ ch.close()
+ yield()
+ }
+
+ yield()
+
+ expect(3)
+
+ val line = ch.readUTF8Line()
+ expect(5)
+ assertEquals("ABC", line)
+
+ finish(6)
+ }
+
+ @Test
+ fun testReadTailReadFirst() = runBlocking {
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(3)
+
+ ch.writeFully("ABC".toByteArray())
+
+ yield()
+
+ expect(4)
+ ch.close()
+ yield()
+ }
+
+ expect(2)
+
+ val line = ch.readUTF8Line()
+ expect(5)
+ assertEquals("ABC", line)
+
+ finish(6)
+ }
+
+ @Test
+ fun testReadThroughWrap() = runBlocking {
+ val L = ".".repeat(128)
+
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+
+ ch.writeFully(ByteArray(4000))
+
+ expect(3)
+ ch.readFully(ByteArray(3999)) // keep one byte remaining to keep buffer unreleased
+
+ expect(4)
+
+ ch.writeFully(L.toByteArray())
+
+ expect(5)
+ ch.close()
+ }
+
+ yield()
+
+ expect(6)
+
+ ch.readByte()
+ expect(7)
+
+ val line = ch.readUTF8Line()
+
+ finish(8)
+
+ assertEquals(L, line)
+ }
+
+ @Test
+ fun testReadShifted() = runBlocking {
+ val L = ".".repeat(127) + "\n"
+ var base = 0
+
+ for (shift in 1..4096 - 8) {
+ expect(base + 1)
+
+ launch(coroutineContext) {
+ expect(base + 2)
+
+ ch.writeFully(ByteArray(shift))
+
+ expect(base + 3)
+ ch.readFully(ByteArray(shift - 1)) // keep one byte remaining to keep buffer unreleased
+
+ expect(base + 4)
+
+ ch.writeFully(L.toByteArray())
+
+ expect(base + 5)
+ }
+
+ yield()
+
+ expect(base + 6)
+
+ ch.readByte()
+ expect(base + 7)
+
+ val line = ch.readUTF8Line()
+
+ expect(base + 8)
+
+ assertEquals(L.dropLast(1), line)
+
+ base += 8
+ }
+
+ finish(base + 1)
+ }
+
+ @Test
+ fun writeLongLine() = runBlocking {
+ val L = ".".repeat(16384)
+
+ expect(1)
+
+ launch(coroutineContext) {
+ expect(2)
+
+ ch.writeFully(L.toByteArray())
+
+ expect(4)
+ ch.close()
+ }
+
+ yield()
+
+ expect(3)
+ val line = ch.readUTF8Line()
+
+ expect(5)
+
+ assertEquals(L, line)
+
+ finish(6)
+ }
+}
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/StringsTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/StringsTest.kt
new file mode 100644
index 0000000..ca7ee86
--- /dev/null
+++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/StringsTest.kt
@@ -0,0 +1,305 @@
+package kotlinx.coroutines.experimental.io
+
+import kotlinx.coroutines.experimental.*
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.Timeout
+import java.util.*
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+class StringsTest {
+ @get:Rule
+ val timeout = Timeout(10, TimeUnit.SECONDS)
+
+ private val channel = ByteBufferChannel(autoFlush = true)
+
+ @Test
+ fun testReadString() {
+ runBlocking {
+ writeString("Hello, World!")
+ channel.close()
+ assertEquals("Hello, World!", channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testReadLines1() {
+ testReadLine("\r", "", "")
+ }
+
+ @Test
+ fun testReadLinesCases() {
+ testReadLine("abc", "abc", "")
+ testReadLine("", null, "")
+
+ testReadLine("\n", "", "")
+ testReadLine("\r", "", "")
+ testReadLine("\r\n", "", "")
+ testReadLine("1\n", "1", "")
+ testReadLine("1\r", "1", "")
+ testReadLine("1\r\n", "1", "")
+
+ testReadLine("\n2", "", "2")
+ testReadLine("\r2", "", "2")
+ testReadLine("\r\n2", "", "2")
+ testReadLine("1\n2", "1", "2")
+ testReadLine("1\r2", "1", "2")
+ testReadLine("1\r\n2", "1", "2")
+
+ // unicode
+ testReadLine("\u0440\n", "\u0440", "")
+ testReadLine("\u0440\n1", "\u0440", "1")
+ testReadLine("\u0440\r", "\u0440", "")
+ testReadLine("\u0440\r2", "\u0440", "2")
+ }
+
+ private fun testReadLine(source: String, expectedLine: String?, expectedRemaining: String) {
+ val content = source.toByteArray(Charsets.UTF_8)
+
+ // no splitting
+ runBlocking {
+ val ch = ByteReadChannel(content)
+// testReadLine(ch, expectedLine, expectedRemaining)
+ }
+
+ // split
+ for (splitAt in 0 until content.size) {
+ val ch = ByteChannel(true)
+ runBlocking {
+ launch(coroutineContext) {
+ ch.writeFully(content, 0, splitAt)
+ yield()
+ ch.writeFully(content, splitAt, content.size - splitAt)
+ ch.close()
+ }
+
+ testReadLine(ch, expectedLine, expectedRemaining)
+ }
+ }
+ }
+
+ private suspend fun testReadLine(ch: ByteReadChannel, expectedLine: String?, expectedRemaining: String) {
+ val line = ch.readUTF8Line()
+ assertEquals(expectedLine, line)
+
+ val buffer = ByteBuffer.allocate(8192)
+ val rc = ch.readAvailable(buffer)
+
+ if (expectedRemaining.isNotEmpty()) {
+ assertNotEquals(-1, rc, "Unexpected EOF. Expected >= 0")
+ }
+
+ buffer.flip()
+ assertEquals(expectedRemaining, Charsets.UTF_8.decode(buffer).toString())
+ }
+
+ @Test
+ fun testReadLines() {
+ runBlocking {
+ writeString("Hello, World!\nLine2")
+ assertEquals("Hello, World!", channel.readASCIILine())
+ channel.close()
+ assertEquals("Line2", channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testReadASCIILineLf() {
+ runBlocking {
+ writeParts("A", "B\n", "C")
+
+ assertEquals("AB", channel.readASCIILine())
+ assertEquals("C", channel.readASCIILine())
+ assertEquals(null, channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testReadASCIILineCrLf() {
+ runBlocking {
+ writeParts("A", "B\r\n", "C")
+
+ assertEquals("AB", channel.readASCIILine())
+ assertEquals("C", channel.readASCIILine())
+ assertEquals(null, channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testReadASCIILineCrLfBadSplit() {
+ runBlocking {
+ writeParts("A", "B\r", "\nC")
+
+ assertEquals("AB", channel.readASCIILine())
+ assertEquals("C", channel.readASCIILine())
+ assertEquals(null, channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testReadASCIILineTrailingLf() {
+ runBlocking {
+ writeParts("A", "B\n", "C\n")
+
+ assertEquals("AB", channel.readASCIILine())
+ assertEquals("C", channel.readASCIILine())
+ assertEquals(null, channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testReadASCIILineLeadingLf() {
+ runBlocking {
+ writeParts("\nA", "B\n", "C")
+
+ assertEquals("", channel.readASCIILine())
+ assertEquals("AB", channel.readASCIILine())
+ assertEquals("C", channel.readASCIILine())
+ assertEquals(null, channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testLookAhead() {
+ val text = buildString() {
+ for (i in 0 until 65535) {
+ append((i and 0xf).toString(16))
+ }
+ }.toByteArray()
+
+ runBlocking {
+ launch(CommonPool) {
+ channel.writeFully(text)
+ channel.close()
+ }
+
+ val comparison = ByteBuffer.wrap(text)
+
+ val arr = ByteArray(128)
+ var rem = text.size
+ val rnd = Random()
+
+ while (rem > 0) {
+ val s = rnd.nextInt(arr.size).coerceIn(1, rem)
+ arr.fill(0)
+ val rc = channel.readAvailable(arr, 0, s)
+
+ if (rc == -1) fail("EOF")
+
+ val actual = String(arr, 0, rc)
+
+ val expectedBytes = ByteArray(rc)
+ comparison.get(expectedBytes)
+ val expected = expectedBytes.toString(Charsets.ISO_8859_1)
+
+ assertEquals(expected, actual)
+
+ rem -= rc
+ }
+ }
+ }
+
+ @Test
+ fun testLongLinesConcurrent() {
+ val lines = (0..1024).map { size ->
+ buildString(size) {
+ for (i in 0 until size) {
+ append((i and 0xf).toString(16))
+ }
+ }
+ }
+
+ runBlocking {
+ launch(CommonPool) {
+ for (part in lines) {
+ writeString(part + "\n")
+ }
+ channel.close()
+ }
+
+ for (expected in lines) {
+ assertEquals(expected, channel.readASCIILine(expected.length))
+ }
+
+ assertNull(channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testLongLinesSequential() {
+ val lines = (0..1024).map { size ->
+ buildString(size) {
+ for (i in 0 until size) {
+ append((i and 0xf).toString(16))
+ }
+ }
+ }
+
+ runBlocking {
+ launch(coroutineContext) {
+ for (part in lines) {
+ writeString(part + "\n")
+ yield()
+ }
+ channel.close()
+ }
+
+ for (expected in lines) {
+ Thread.yield()
+ assertEquals(expected, channel.readASCIILine(expected.length))
+ }
+
+ assertNull(channel.readASCIILine())
+ }
+ }
+
+ @Test
+ fun testReadUTF8Line2bytes() {
+ val parts = byteArrayOf(0xd0.toByte(), 0x9a.toByte(), 0x0a)
+
+ runBlocking {
+ channel.writeFully(parts)
+ assertEquals("\u041a", channel.readUTF8Line())
+ }
+ }
+
+ @Test
+ fun testReadUTF8Line3bytes() {
+ val parts = byteArrayOf(0xe0.toByte(), 0xaf.toByte(), 0xb5.toByte(), 0x0a)
+
+ runBlocking {
+ channel.writeFully(parts)
+ assertEquals("\u0BF5", channel.readUTF8Line())
+ }
+ }
+
+ @Test
+ fun testReadUTF8Line4bytes() {
+ val parts = byteArrayOf(0xF0.toByte(), 0xA6.toByte(), 0x88.toByte(), 0x98.toByte(), 0x0a)
+
+ runBlocking {
+ channel.writeFully(parts)
+ assertEquals("\uD858\uDE18", channel.readUTF8Line())
+ }
+ }
+
+ private suspend fun writeString(s: String) {
+ channel.writeFully(s.toByteArray(Charsets.ISO_8859_1))
+ }
+
+ private fun writeParts(vararg parts: String) {
+ launch(CommonPool) {
+ parts.forEach { p ->
+ writeString(p)
+ yield()
+ delay(1)
+ }
+
+ channel.close()
+ }
+ }
+}
\ No newline at end of file
diff --git a/coroutines-guide.md b/coroutines-guide.md
index 109bf35..409602f 100644
--- a/coroutines-guide.md
+++ b/coroutines-guide.md
@@ -20,8 +20,8 @@
import kotlinx.coroutines.experimental.*
-->
-<!--- KNIT kotlinx-coroutines-core/src/test/kotlin/guide/.*\.kt -->
-<!--- TEST_OUT kotlinx-coroutines-core/src/test/kotlin/guide/test/GuideTest.kt
+<!--- KNIT core/kotlinx-coroutines-core/src/test/kotlin/guide/.*\.kt -->
+<!--- TEST_OUT core/kotlinx-coroutines-core/src/test/kotlin/guide/test/GuideTest.kt
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
package guide.test
@@ -76,6 +76,7 @@
* [Job in the context](#job-in-the-context)
* [Children of a coroutine](#children-of-a-coroutine)
* [Combining contexts](#combining-contexts)
+ * [Parental responsibilities](#parental-responsibilities)
* [Naming coroutines for debugging](#naming-coroutines-for-debugging)
* [Cancellation via explicit job](#cancellation-via-explicit-job)
* [Channels](#channels)
@@ -116,7 +117,7 @@
```kotlin
fun main(args: Array<String>) {
- launch(CommonPool) { // create new coroutine in common thread pool
+ launch { // launch new coroutine
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
@@ -125,7 +126,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-01.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-01.kt)
Run this code:
@@ -139,9 +140,9 @@
Essentially, coroutines are light-weight threads.
They are launched with [launch] _coroutine builder_.
You can achieve the same result replacing
-`launch(CommonPool) { ... }` with `thread { ... }` and `delay(...)` with `Thread.sleep(...)`. Try it.
+`launch { ... }` with `thread { ... }` and `delay(...)` with `Thread.sleep(...)`. Try it.
-If you start by replacing `launch(CommonPool)` by `thread`, the compiler produces the following error:
+If you start by replacing `launch` by `thread`, the compiler produces the following error:
```
Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function
@@ -158,7 +159,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
- launch(CommonPool) { // create new coroutine in common thread pool
+ launch { // launch new coroutine
delay(1000L)
println("World!")
}
@@ -167,7 +168,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-02.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-02.kt)
<!--- TEST
Hello,
@@ -199,7 +200,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) { // create new coroutine and keep a reference to its Job
+ val job = launch { // launch new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
@@ -208,7 +209,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-03.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-03.kt)
<!--- TEST
Hello,
@@ -220,7 +221,7 @@
### Extract function refactoring
-Let's extract the block of code inside `launch(CommonPool) { ... }` into a separate function. When you
+Let's extract the block of code inside `launch { ... }` into a separate function. When you
perform "Extract function" refactoring on this code you get a new function with `suspend` modifier.
That is your first _suspending function_. Suspending functions can be used inside coroutines
just like regular functions, but their additional feature is that they can, in turn,
@@ -228,7 +229,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) { doWorld() }
+ val job = launch { doWorld() }
println("Hello,")
job.join()
}
@@ -240,7 +241,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-04.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-04.kt)
<!--- TEST
Hello,
@@ -253,8 +254,8 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
- val jobs = List(100_000) { // create a lot of coroutines and list their jobs
- launch(CommonPool) {
+ val jobs = List(100_000) { // launch a lot of coroutines and list their jobs
+ launch {
delay(1000L)
print(".")
}
@@ -263,11 +264,11 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-05.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-05.kt)
<!--- TEST lines.size == 1 && lines[0] == ".".repeat(100_000) -->
-It starts 100K coroutines and, after a second, each coroutine prints a dot.
+It launches 100K coroutines and, after a second, each coroutine prints a dot.
Now, try that with threads. What would happen? (Most likely your code will produce some sort of out-of-memory error)
### Coroutines are like daemon threads
@@ -277,7 +278,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
- launch(CommonPool) {
+ launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
@@ -287,7 +288,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-basic-06.kt)
You can run and see that it prints three lines and terminates:
@@ -313,7 +314,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) {
+ val job = launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
@@ -322,12 +323,12 @@
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
- delay(1300L) // delay a bit to ensure it was cancelled indeed
+ job.join() // waits for job's completion
println("main: Now I can quit.")
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-01.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-01.kt)
It produces the following output:
@@ -342,6 +343,8 @@
<!--- TEST -->
As soon as main invokes `job.cancel`, we don't see any output from the other coroutine because it was cancelled.
+There is also a [Job] extension function [cancelAndJoin]
+that combines [cancel][Job.cancel] and [join][Job.join] invocations.
### Cancellation is cooperative
@@ -354,10 +357,10 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
- val job = launch(CommonPool) {
+ val job = launch {
var nextPrintTime = startTime
var i = 0
- while (i < 10) { // computation loop, just wastes CPU
+ while (i < 5) { // computation loop, just wastes CPU
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("I'm sleeping ${i++} ...")
@@ -367,15 +370,15 @@
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- delay(1300L) // delay a bit to see if it was cancelled....
+ job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-02.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-02.kt)
-Run it to see that it continues to print "I'm sleeping" even after cancellation.
+Run it to see that it continues to print "I'm sleeping" even after cancellation
+until the job completes by itself after five iterations.
<!--- TEST
I'm sleeping 0 ...
@@ -384,22 +387,21 @@
main: I'm tired of waiting!
I'm sleeping 3 ...
I'm sleeping 4 ...
-I'm sleeping 5 ...
main: Now I can quit.
-->
### Making computation code cancellable
There are two approaches to making computation code cancellable. The first one is to periodically
-invoke a suspending function. There is a [yield] function that is a good choice for that purpose.
+invoke a suspending function that checks for cancellation. There is a [yield] function that is a good choice for that purpose.
The other one is to explicitly check the cancellation status. Let us try the later approach.
-Replace `while (i < 10)` in the previous example with `while (isActive)` and rerun it.
+Replace `while (i < 5)` in the previous example with `while (isActive)` and rerun it.
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
- val job = launch(CommonPool) {
+ val job = launch {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
@@ -412,15 +414,14 @@
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- delay(1300L) // delay a bit to see if it was cancelled....
+ job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-03.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-03.kt)
-As you can see, now this loop can be cancelled. [isActive][CoroutineScope.isActive] is a property that is available inside
+As you can see, now this loop is cancelled. [isActive][CoroutineScope.isActive] is a property that is available inside
the code of coroutines via [CoroutineScope] object.
<!--- TEST
@@ -434,12 +435,12 @@
### Closing resources with finally
Cancellable suspending functions throw [CancellationException] on cancellation which can be handled in
-all the usual way. For example, the `try {...} finally {...}` and Kotlin `use` function execute their
+all the usual way. For example, `try {...} finally {...}` expression and Kotlin `use` function execute their
finalization actions normally when coroutine is cancelled:
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) {
+ val job = launch {
try {
repeat(1000) { i ->
println("I'm sleeping $i ...")
@@ -451,15 +452,15 @@
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- delay(1300L) // delay a bit to ensure it was cancelled indeed
+ job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-04.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-04.kt)
-The example above produces the following output:
+Both [join][Job.join] and [cancelAndJoin] wait for all the finalization actions to complete,
+so the example above produces the following output:
```text
I'm sleeping 0 ...
@@ -483,7 +484,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
- val job = launch(CommonPool) {
+ val job = launch {
try {
repeat(1000) { i ->
println("I'm sleeping $i ...")
@@ -499,13 +500,12 @@
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- delay(1300L) // delay a bit to ensure it was cancelled indeed
+ job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-05.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-05.kt)
<!--- TEST
I'm sleeping 0 ...
@@ -536,7 +536,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-06.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-06.kt)
It produces the following output:
@@ -544,19 +544,46 @@
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
-Exception in thread "main" kotlinx.coroutines.experimental.TimeoutException: Timed out waiting for 1300 MILLISECONDS
+Exception in thread "main" kotlinx.coroutines.experimental.TimeoutCancellationException: Timed out waiting for 1300 MILLISECONDS
```
<!--- TEST STARTS_WITH -->
-The `TimeoutException` that is thrown by [withTimeout] is a private subclass of [CancellationException].
+The `TimeoutCancellationException` that is thrown by [withTimeout] is a subclass of [CancellationException].
We have not seen its stack trace printed on the console before. That is because
inside a cancelled coroutine `CancellationException` is considered to be a normal reason for coroutine completion.
However, in this example we have used `withTimeout` right inside the `main` function.
Because cancellation is just an exception, all the resources will be closed in a usual way.
-You can wrap the code with timeout in `try {...} catch (e: CancellationException) {...}` block if
-you need to do some additional action specifically on timeout.
+You can wrap the code with timeout in `try {...} catch (e: TimeoutCancellationException) {...}` block if
+you need to do some additional action specifically on any kind of timeout or use [withTimeoutOrNull] function
+that is similar to [withTimeout], but returns `null` on timeout instead of throwing an exception:
+
+```kotlin
+fun main(args: Array<String>) = runBlocking<Unit> {
+ val result = withTimeoutOrNull(1300L) {
+ repeat(1000) { i ->
+ println("I'm sleeping $i ...")
+ delay(500L)
+ }
+ "Done" // will get cancelled before it produces this result
+ }
+ println("Result is $result")
+}
+```
+
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-cancel-07.kt)
+
+There is no longer an exception when running this code:
+
+```text
+I'm sleeping 0 ...
+I'm sleeping 1 ...
+I'm sleeping 2 ...
+Result is null
+```
+
+<!--- TEST -->
## Composing suspending functions
@@ -606,7 +633,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt)
It produces something like this:
@@ -631,15 +658,15 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
- val one = async(CommonPool) { doSomethingUsefulOne() }
- val two = async(CommonPool) { doSomethingUsefulTwo() }
+ val one = async { doSomethingUsefulOne() }
+ val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt)
It produces something like this:
@@ -655,7 +682,7 @@
### Lazily started async
-There is a laziness option to [async] with [CoroutineStart.LAZY] parameter.
+There is a laziness option to [async] using an optional `start` parameter with a value of [CoroutineStart.LAZY].
It starts coroutine only when its result is needed by some
[await][Deferred.await] or if a [start][Job.start] function
is invoked. Run the following example that differs from the previous one only by this option:
@@ -663,15 +690,15 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
- val one = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulOne() }
- val two = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulTwo() }
+ val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
+ val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt)
It produces something like this:
@@ -695,12 +722,12 @@
```kotlin
// The result type of asyncSomethingUsefulOne is Deferred<Int>
-fun asyncSomethingUsefulOne() = async(CommonPool) {
+fun asyncSomethingUsefulOne() = async {
doSomethingUsefulOne()
}
// The result type of asyncSomethingUsefulTwo is Deferred<Int>
-fun asyncSomethingUsefulTwo() = async(CommonPool) {
+fun asyncSomethingUsefulTwo() = async {
doSomethingUsefulTwo()
}
```
@@ -728,7 +755,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt)
<!--- TEST ARBITRARY_TIME
The answer is 42
@@ -737,15 +764,24 @@
## Coroutine context and dispatchers
-We've already seen `launch(CommonPool) {...}`, `async(CommonPool) {...}`, `run(NonCancellable) {...}`, etc.
-In these code snippets [CommonPool] and [NonCancellable] are _coroutine contexts_.
-This section covers other available choices.
+Coroutines always execute in some context which is represented by the value of
+[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/-coroutine-context/)
+type, defined in the Kotlin standard library.
+
+The coroutine context is a set of various elements. The main elements are the [Job] of the coroutine,
+which we've seen before, and its dispatcher, which is covered in this section.
### Dispatchers and threads
-Coroutine context includes a [_coroutine dispatcher_][CoroutineDispatcher] which determines what thread or threads
+Coroutine context includes a _coroutine dispatcher_ (see [CoroutineDispatcher]) that determines what thread or threads
the corresponding coroutine uses for its execution. Coroutine dispatcher can confine coroutine execution
-to a specific thread, dispatch it to a thread pool, or let it run unconfined. Try the following example:
+to a specific thread, dispatch it to a thread pool, or let it run unconfined.
+
+All coroutines builders like [launch] and [async] accept an optional
+[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/-coroutine-context/)
+parameter that can be used to explicitly specify the dispatcher for new coroutine and other context elements.
+
+Try the following example:
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
@@ -766,7 +802,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt)
It produces the following output (maybe in different order):
@@ -779,6 +815,10 @@
<!--- TEST LINES_START_UNORDERED -->
+The default dispatcher that we've used in previous sections is representend by [DefaultDispather], which
+is equal to [CommonPool] in the current implementation. So, `launch { ... }` is the same
+as `launch(DefaultDispather) { ... }`, which is the same as `launch(CommonPool) { ... }`.
+
The difference between parent [coroutineContext][CoroutineScope.coroutineContext] and
[Unconfined] context will be shown later.
@@ -791,8 +831,8 @@
On the other side, [coroutineContext][CoroutineScope.coroutineContext] property that is available inside the block of any coroutine
via [CoroutineScope] interface, is a reference to a context of this particular coroutine.
-This way, a parent context can be inherited. The default context of [runBlocking], in particular,
-is confined to be invoker thread, so inheriting it has the effect of confining execution to
+This way, a parent context can be inherited. The default dispatcher for [runBlocking] coroutine, in particular,
+is confined to the invoker thread, so inheriting it has the effect of confining execution to
this thread with a predictable FIFO scheduling.
```kotlin
@@ -812,7 +852,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt)
Produces the output:
@@ -832,7 +872,7 @@
### Debugging coroutines and threads
Coroutines can suspend on one thread and resume on another thread with [Unconfined] dispatcher or
-with a multi-threaded dispatcher like [CommonPool]. Even with a single-threaded dispatcher it might be hard to
+with a default multi-threaded dispatcher. Even with a single-threaded dispatcher it might be hard to
figure out what coroutine was doing what, where, and when. The common approach to debugging applications with
threads is to print the thread name in the log file on each log statement. This feature is universally supported
by logging frameworks. When using coroutines, the thread name alone does not give much of a context, so
@@ -856,7 +896,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt)
There are three coroutines. The main coroutine (#1) -- `runBlocking` one,
and two coroutines computing deferred values `a` (#2) and `b` (#3).
@@ -897,7 +937,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-04.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-04.kt)
It demonstrates two new techniques. One is using [runBlocking] with an explicitly specified context, and
the second one is using [run] function to change a context of a coroutine while still staying in the
@@ -913,7 +953,7 @@
### Job in the context
-The coroutine [Job] is part of its context. The coroutine can retrieve it from its own context
+The coroutine's [Job] is part of its context. The coroutine can retrieve it from its own context
using `coroutineContext[Job]` expression:
```kotlin
@@ -922,15 +962,15 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt)
-It produces something like
+It produces something like that when running in [debug mode](#debugging-coroutines-and-threads):
```
-My job is BlockingCoroutine{Active}@65ae6ba4
+My job is "coroutine#1":BlockingCoroutine{Active}@6d311334
```
-<!--- TEST lines.size == 1 && lines[0].startsWith("My job is BlockingCoroutine{Active}@") -->
+<!--- TEST lines.size == 1 && lines[0].startsWith("My job is \"coroutine#1\":BlockingCoroutine{Active}@") -->
So, [isActive][CoroutineScope.isActive] in [CoroutineScope] is just a convenient shortcut for
`coroutineContext[Job]!!.isActive`.
@@ -944,10 +984,10 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
- // start a coroutine to process some kind of incoming request
- val request = launch(CommonPool) {
+ // launch a coroutine to process some kind of incoming request
+ val request = launch {
// it spawns two other jobs, one with its separate context
- val job1 = launch(CommonPool) {
+ val job1 = launch {
println("job1: I have my own context and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
@@ -969,7 +1009,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt)
The output of this code is:
@@ -984,7 +1024,7 @@
### Combining contexts
-Coroutine context can be combined using `+` operator. The context on the right-hand side replaces relevant entries
+Coroutine contexts can be combined using `+` operator. The context on the right-hand side replaces relevant entries
of the context on the left-hand side. For example, a [Job] of the parent coroutine can be inherited, while
its dispatcher replaced:
@@ -1007,7 +1047,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt)
The expected outcome of this code is:
@@ -1018,13 +1058,49 @@
<!--- TEST -->
+### Parental responsibilities
+
+A parent coroutine always waits for completion of all its children. Parent does not have to explicitly track
+all the children it launches and it does not have to use [Job.join] to wait for them at the end:
+
+```kotlin
+fun main(args: Array<String>) = runBlocking<Unit> {
+ // launch a coroutine to process some kind of incoming request
+ val request = launch {
+ repeat(3) { i -> // launch a few children jobs
+ launch(coroutineContext) {
+ delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
+ println("Coroutine $i is done")
+ }
+ }
+ println("request: I'm done and I don't explicitly join my children that are still active")
+ }
+ request.join() // wait for completion of the request, including all its children
+ println("Now processing of the request is complete")
+}
+```
+
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-08.kt)
+
+The result is going to be:
+
+```text
+request: I'm done and I don't explicitly join my children that are still active
+Coroutine 0 is done
+Coroutine 1 is done
+Coroutine 2 is done
+Now processing of the request is complete
+```
+
+<!--- TEST -->
+
### Naming coroutines for debugging
Automatically assigned ids are good when coroutines log often and you just need to correlate log records
coming from the same coroutine. However, when coroutine is tied to the processing of a specific request
or doing some specific background task, it is better to name it explicitly for debugging purposes.
-[CoroutineName] serves the same function as a thread name. It'll get displayed in the thread name that
-is executing this coroutine when debugging mode is turned on.
+[CoroutineName] context element serves the same function as a thread name. It'll get displayed in the thread name that
+is executing this coroutine when [debugging mode](#debugging-coroutines-and-threads) is turned on.
The following example demonstrates this concept:
@@ -1034,12 +1110,12 @@
fun main(args: Array<String>) = runBlocking(CoroutineName("main")) {
log("Started main coroutine")
// run two background value computations
- val v1 = async(CommonPool + CoroutineName("v1coroutine")) {
+ val v1 = async(CoroutineName("v1coroutine")) {
log("Computing v1")
delay(500)
252
}
- val v2 = async(CommonPool + CoroutineName("v2coroutine")) {
+ val v2 = async(CoroutineName("v2coroutine")) {
log("Computing v2")
delay(1000)
6
@@ -1048,7 +1124,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-08.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-09.kt)
The output it produces with `-Dkotlinx.coroutines.debug` JVM option is similar to:
@@ -1070,9 +1146,11 @@
to avoid memory leaks.
We can manage a lifecycle of our coroutines by creating an instance of [Job] that is tied to
-the lifecycle of our activity. A job instance is created using [`Job()`][Job] factory function
+the lifecycle of our activity. A job instance is created using [Job()] factory function
as the following example shows. We need to make sure that all the coroutines are started
with this job in their context and then a single invocation of [Job.cancel] terminates them all.
+Moreover, [Job.join] waits for all of them to complete, so we can also use [cancelAndJoin] here in
+this example:
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
@@ -1081,19 +1159,18 @@
val coroutines = List(10) { i ->
// they are all children of our job object
launch(coroutineContext + job) { // we use the context of main runBlocking thread, but with our own job object
- delay(i * 200L) // variable delay 0ms, 200ms, 400ms, ... etc
+ delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
println("Coroutine $i is done")
}
}
println("Launched ${coroutines.size} coroutines")
delay(500L) // delay for half a second
- println("Cancelling job!")
- job.cancel() // cancel our job.. !!!
- delay(1000L) // delay for more to see if our coroutines are still working
+ println("Cancelling the job!")
+ job.cancelAndJoin() // cancel all our coroutines and wait for all of them to complete
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-context-09.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt)
The output of this example is:
@@ -1101,16 +1178,17 @@
Launched 10 coroutines
Coroutine 0 is done
Coroutine 1 is done
-Coroutine 2 is done
-Cancelling job!
+Cancelling the job!
```
<!--- TEST -->
As you can see, only the first three coroutines had printed a message and the others were cancelled
-by a single invocation of `job.cancel()`. So all we need to do in our hypothetical Android
+by a single invocation of `job.cancelAndJoin()`. So all we need to do in our hypothetical Android
application is to create a parent job object when activity is created, use it for child coroutines,
-and cancel it when activity is destroyed.
+and cancel it when activity is destroyed. We cannot `join` them in the case of Android lifecycle,
+since it is synchronous, but this joining ability is useful when building backend services to ensure bounded
+resource usage.
## Channels
@@ -1130,7 +1208,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
val channel = Channel<Int>()
- launch(CommonPool) {
+ launch {
// this might be heavy CPU-consuming computation or async logic, we'll just send five squares
for (x in 1..5) channel.send(x * x)
}
@@ -1140,7 +1218,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-01.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-01.kt)
The output of this code is:
@@ -1168,7 +1246,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
val channel = Channel<Int>()
- launch(CommonPool) {
+ launch {
for (x in 1..5) channel.send(x * x)
channel.close() // we're done sending
}
@@ -1178,7 +1256,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-02.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-02.kt)
<!--- TEST
1
@@ -1197,10 +1275,10 @@
to common sense that results must be returned from functions.
There is a convenience coroutine builder named [produce] that makes it easy to do it right on producer side,
-and an extension function [consumeEach], that can replace a `for` loop on the consumer side:
+and an extension function [consumeEach], that replaces a `for` loop on the consumer side:
```kotlin
-fun produceSquares() = produce<Int>(CommonPool) {
+fun produceSquares() = produce<Int> {
for (x in 1..5) send(x * x)
}
@@ -1211,7 +1289,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-03.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-03.kt)
<!--- TEST
1
@@ -1224,10 +1302,10 @@
### Pipelines
-Pipeline is a pattern where one coroutine is producing, possibly infinite, stream of values:
+A pipeline is a pattern where one coroutine is producing, possibly infinite, stream of values:
```kotlin
-fun produceNumbers() = produce<Int>(CommonPool) {
+fun produceNumbers() = produce<Int> {
var x = 1
while (true) send(x++) // infinite stream of integers starting from 1
}
@@ -1237,7 +1315,7 @@
In the below example the numbers are just squared:
```kotlin
-fun square(numbers: ReceiveChannel<Int>) = produce<Int>(CommonPool) {
+fun square(numbers: ReceiveChannel<Int>) = produce<Int> {
for (x in numbers) send(x * x)
}
```
@@ -1255,7 +1333,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-04.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-04.kt)
<!--- TEST
1
@@ -1270,15 +1348,16 @@
[coroutines are like daemon threads](#coroutines-are-like-daemon-threads),
but in a larger app we'll need to stop our pipeline if we don't need it anymore.
Alternatively, we could have run pipeline coroutines as
-[children of a coroutine](#children-of-a-coroutine).
+[children of a main coroutine](#children-of-a-coroutine) as is demonstrated in the following example.
### Prime numbers with pipeline
Let's take pipelines to the extreme with an example that generates prime numbers using a pipeline
of coroutines. We start with an infinite sequence of numbers. This time we introduce an
-explicit context parameter, so that caller can control where our coroutines run:
+explicit `context` parameter and pass it to [produce] builder,
+so that caller can control where our coroutines run:
-<!--- INCLUDE kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt
+<!--- INCLUDE core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt
import kotlin.coroutines.experimental.CoroutineContext
-->
@@ -1306,7 +1385,10 @@
```
The following example prints the first ten prime numbers,
-running the whole pipeline in the context of the main thread:
+running the whole pipeline in the context of the main thread. Since all the coroutines are launched as
+children of the main [runBlocking] coroutine in its [coroutineContext][CoroutineScope.coroutineContext],
+we don't have to keep an explicit list of all the coroutine we have started.
+We use [cancelChildren] extension function to cancel all the children coroutines.
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
@@ -1316,10 +1398,11 @@
println(prime)
cur = filter(coroutineContext, cur, prime)
}
+ coroutineContext.cancelChildren() // cancel all children to let main finish
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt)
The output of this code is:
@@ -1338,7 +1421,9 @@
<!--- TEST -->
-Note, that you can build the same pipeline using `buildIterator` coroutine builder from the standard library.
+Note, that you can build the same pipeline using
+[`buildIterator`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/build-iterator.html)
+coroutine builder from the standard library.
Replace `produce` with `buildIterator`, `send` with `yield`, `receive` with `next`,
`ReceiveChannel` with `Iterator`, and get rid of the context. You will not need `runBlocking` either.
However, the benefit of a pipeline that uses channels as shown above is that it can actually use
@@ -1347,7 +1432,7 @@
Anyway, this is an extremely impractical way to find prime numbers. In practice, pipelines do involve some
other suspending invocations (like asynchronous calls to remote services) and these pipelines cannot be
built using `buildSeqeunce`/`buildIterator`, because they do not allow arbitrary suspension, unlike
-`produce` which is fully asynchronous.
+`produce`, which is fully asynchronous.
### Fan-out
@@ -1356,7 +1441,7 @@
(ten numbers per second):
```kotlin
-fun produceNumbers() = produce<Int>(CommonPool) {
+fun produceNumbers() = produce<Int> {
var x = 1 // start from 1
while (true) {
send(x++) // produce next
@@ -1369,7 +1454,7 @@
received number:
```kotlin
-fun launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch(CommonPool) {
+fun launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
channel.consumeEach {
println("Processor #$id received $it")
}
@@ -1387,7 +1472,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-06.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-06.kt)
The output will be similar to the the following one, albeit the processor ids that receive
each specific integer may be different:
@@ -1426,7 +1511,7 @@
```
Now, let us see what happens if we launch a couple of coroutines sending strings
-(in this example we launch them in the context of the main thread):
+(in this example we launch them in the context of the main thread as main coroutine's children):
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
@@ -1436,10 +1521,11 @@
repeat(6) { // receive first six
println(channel.receive())
}
+ coroutineContext.cancelChildren() // cancel all children to let main finish
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt)
The output is:
@@ -1460,7 +1546,7 @@
meet each other (aka rendezvous). If send is invoked first, then it is suspended until receive is invoked,
if receive is invoked first, it is suspended until send is invoked.
-Both [`Channel()`][Channel] factory function and [produce] builder take an optional `capacity` parameter to
+Both [Channel()] factory function and [produce] builder take an optional `capacity` parameter to
specify _buffer size_. Buffer allows senders to send multiple elements before suspending,
similar to the `BlockingQueue` with a specified capacity, which blocks when buffer is full.
@@ -1469,7 +1555,7 @@
```kotlin
fun main(args: Array<String>) = runBlocking<Unit> {
val channel = Channel<Int>(4) // create buffered channel
- launch(coroutineContext) { // launch sender coroutine
+ val sender = launch(coroutineContext) { // launch sender coroutine
repeat(10) {
println("Sending $it") // print before sending each element
channel.send(it) // will suspend when buffer is full
@@ -1477,10 +1563,11 @@
}
// don't receive anything... just wait....
delay(1000)
+ sender.cancel() // cancel sender coroutine
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt)
It prints "sending" _five_ times using a buffered channel with capacity of _four_:
@@ -1496,7 +1583,6 @@
The first four elements are added to the buffer and the sender suspends when trying to send the fifth one.
-
### Channels are fair
Send and receive operations to channels are _fair_ with respect to the order of their invocation from
@@ -1513,7 +1599,7 @@
launch(coroutineContext) { player("pong", table) }
table.send(Ball(0)) // serve the ball
delay(1000) // delay 1 second
- table.receive() // game over, grab the ball
+ coroutineContext.cancelChildren() // game over, cancel them
}
suspend fun player(name: String, table: Channel<Ball>) {
@@ -1526,7 +1612,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt)
The "ping" coroutine is started first, so it is the first one to receive the ball. Even though "ping"
coroutine immediately starts receiving the ball again after sending it back to the table, the ball gets
@@ -1537,14 +1623,16 @@
pong Ball(hits=2)
ping Ball(hits=3)
pong Ball(hits=4)
-ping Ball(hits=5)
```
<!--- TEST -->
+Note, that sometimes channels may produce executions that look unfair due to the nature of the executor
+that is being used. See [this issue](https://github.com/Kotlin/kotlinx.coroutines/issues/111) for details.
+
## Shared mutable state and concurrency
-Coroutines can be executed concurrently using a multi-threaded dispatcher like [CommonPool]. It presents
+Coroutines can be executed concurrently using a multi-threaded dispatcher like the default [CommonPool]. It presents
all the usual concurrency problems. The main problem being synchronization of access to **shared mutable state**.
Some solutions to this problem in the land of coroutines are similar to the solutions in the multi-threaded world,
but others are unique.
@@ -1565,6 +1653,7 @@
<!--- INCLUDE .*/example-sync-06.kt
import kotlinx.coroutines.experimental.sync.Mutex
+import kotlinx.coroutines.experimental.sync.withLock
-->
<!--- INCLUDE .*/example-sync-07.kt
@@ -1603,7 +1692,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt)
<!--- TEST LINES_START
Completed 1000000 actions in
@@ -1629,7 +1718,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt)
<!--- TEST LINES_START
Completed 1000000 actions in
@@ -1652,7 +1741,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt)
<!--- TEST LINES_START
Completed 1000000 actions in
@@ -1681,7 +1770,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt)
<!--- TEST ARBITRARY_TIME
Completed 1000000 actions in xxx ms
@@ -1713,7 +1802,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt)
<!--- TEST ARBITRARY_TIME
Completed 1000000 actions in xxx ms
@@ -1741,7 +1830,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt)
<!--- TEST ARBITRARY_TIME
Completed 1000000 actions in xxx ms
@@ -1757,21 +1846,24 @@
Coroutine's alternative is called [Mutex]. It has [lock][Mutex.lock] and [unlock][Mutex.unlock] functions to
delimit a critical section. The key difference is that `Mutex.lock` is a suspending function. It does not block a thread.
+There is also [withLock] extension function that conveniently represents
+`mutex.lock(); try { ... } finally { mutex.unlock() }` pattern:
+
```kotlin
val mutex = Mutex()
var counter = 0
fun main(args: Array<String>) = runBlocking<Unit> {
massiveRun(CommonPool) {
- mutex.lock()
- try { counter++ }
- finally { mutex.unlock() }
+ mutex.withLock {
+ counter++
+ }
}
println("Counter = $counter")
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt)
<!--- TEST ARBITRARY_TIME
Completed 1000000 actions in xxx ms
@@ -1810,7 +1902,7 @@
```kotlin
// This function launches a new counter actor
-fun counterActor() = actor<CounterMsg>(CommonPool) {
+fun counterActor() = actor<CounterMsg> {
var counter = 0 // actor state
for (msg in channel) { // iterate over incoming messages
when (msg) {
@@ -1837,7 +1929,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt)
<!--- TEST ARBITRARY_TIME
Completed 1000000 actions in xxx ms
@@ -1895,7 +1987,7 @@
Using [receive][ReceiveChannel.receive] suspending function we can receive _either_ from one channel or the
other. But [select] expression allows us to receive from _both_ simultaneously using its
-[onReceive][SelectBuilder.onReceive] clauses:
+[onReceive][ReceiveChannel.onReceive] clauses:
```kotlin
suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
@@ -1919,10 +2011,11 @@
repeat(7) {
selectFizzBuzz(fizz, buzz)
}
+ coroutineContext.cancelChildren() // cancel fizz & buzz coroutines
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt)
The result of this code is:
@@ -1940,8 +2033,8 @@
### Selecting on close
-The [onReceive][SelectBuilder.onReceive] clause in `select` fails when the channel is closed and the corresponding
-`select` throws an exception. We can use [onReceiveOrNull][SelectBuilder.onReceiveOrNull] clause to perform a
+The [onReceive][ReceiveChannel.onReceive] clause in `select` fails when the channel is closed and the corresponding
+`select` throws an exception. We can use [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] clause to perform a
specific action when the channel is closed. The following example also shows that `select` is an expression that returns
the result of its selected clause:
@@ -1978,10 +2071,11 @@
repeat(8) { // print first eight results
println(selectAorB(a, b))
}
+ coroutineContext.cancelChildren()
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt)
The result of this code is quite interesting, so we'll analyze it in mode detail:
@@ -2005,19 +2099,23 @@
being the first clause in select, wins. However, because we are using unbuffered channel, the `a` gets suspended from
time to time on its [send][SendChannel.send] invocation and gives a chance for `b` to send, too.
-The second observation, is that [onReceiveOrNull][SelectBuilder.onReceiveOrNull] gets immediately selected when the
+The second observation, is that [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] gets immediately selected when the
channel is already closed.
### Selecting to send
-Select expression has [onSend][SelectBuilder.onSend] clause that can be used for a great good in combination
+Select expression has [onSend][SendChannel.onSend] clause that can be used for a great good in combination
with a biased nature of selection.
Let us write an example of producer of integers that sends its values to a `side` channel when
the consumers on its primary channel cannot keep up with it:
+<!--- INCLUDE
+import kotlin.coroutines.experimental.CoroutineContext
+-->
+
```kotlin
-fun produceNumbers(side: SendChannel<Int>) = produce<Int>(CommonPool) {
+fun produceNumbers(context: CoroutineContext, side: SendChannel<Int>) = produce<Int>(context) {
for (num in 1..10) { // produce 10 numbers from 1 to 10
delay(100) // every 100 ms
select<Unit> {
@@ -2036,15 +2134,16 @@
launch(coroutineContext) { // this is a very fast consumer for the side channel
side.consumeEach { println("Side channel has $it") }
}
- produceNumbers(side).consumeEach {
+ produceNumbers(coroutineContext, side).consumeEach {
println("Consuming $it")
delay(250) // let us digest the consumed number properly, do not hurry
}
println("Done consuming")
+ coroutineContext.cancelChildren()
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt)
So let us see what happens:
@@ -2066,7 +2165,7 @@
### Selecting deferred values
-Deferred values can be selected using [onAwait][SelectBuilder.onAwait] clause.
+Deferred values can be selected using [onAwait][Deferred.onAwait] clause.
Let us start with an async function that returns a deferred string value after
a random delay:
@@ -2075,7 +2174,7 @@
-->
```kotlin
-fun asyncString(time: Int) = async(CommonPool) {
+fun asyncString(time: Int) = async {
delay(time.toLong())
"Waited for $time ms"
}
@@ -2111,7 +2210,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt)
The output is:
@@ -2126,10 +2225,10 @@
Let us write a channel producer function that consumes a channel of deferred string values, waits for each received
deferred value, but only until the next deferred value comes over or the channel is closed. This example puts together
-[onReceiveOrNull][SelectBuilder.onReceiveOrNull] and [onAwait][SelectBuilder.onAwait] clauses in the same `select`:
+[onReceiveOrNull][ReceiveChannel.onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`:
```kotlin
-fun switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String>(CommonPool) {
+fun switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> {
var current = input.receive() // start with first received deferred value
while (isActive) { // loop while not cancelled/closed
val next = select<Deferred<String>?> { // return next deferred value from this select or null
@@ -2154,7 +2253,7 @@
To test it, we'll use a simple async function that resolves to a specified string after a specified time:
```kotlin
-fun asyncString(str: String, time: Long) = async(CommonPool) {
+fun asyncString(str: String, time: Long) = async {
delay(time)
str
}
@@ -2183,7 +2282,7 @@
}
```
-> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt)
+> You can get full code [here](core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt)
The result of this code:
@@ -2209,6 +2308,9 @@
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/delay.html
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/index.html
+[cancelAndJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/cancel-and-join.html
+[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/cancel.html
+[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/join.html
[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-cancellation-exception.html
[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/yield.html
[CoroutineScope.isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/is-active.html
@@ -2216,23 +2318,27 @@
[run]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run.html
[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-non-cancellable/index.html
[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout.html
+[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout-or-null.html
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html
[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html
[CoroutineStart.LAZY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-start/-l-a-z-y.html
[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/await.html
[Job.start]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/start.html
-[CommonPool]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-common-pool/index.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-dispatcher/index.html
+[CommonPool]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-common-pool/index.html
[CoroutineScope.coroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/coroutine-context.html
[Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-unconfined/index.html
[newCoroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-coroutine-context.html
[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-name/index.html
-[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/cancel.html
+[Job()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job.html
+[cancelChildren]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/kotlin.coroutines.experimental.-coroutine-context/cancel-children.html
[CompletableDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-completable-deferred/index.html
+[Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/on-await.html
<!--- INDEX kotlinx.coroutines.experimental.sync -->
[Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/index.html
[Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/lock.html
[Mutex.unlock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/unlock.html
+[withLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/with-lock.html
<!--- INDEX kotlinx.coroutines.experimental.channels -->
[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-channel/index.html
[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/send.html
@@ -2240,11 +2346,11 @@
[SendChannel.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/close.html
[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html
[consumeEach]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/consume-each.html
+[Channel()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-channel.html
[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/actor.html
+[ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/on-receive.html
+[ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/on-receive-or-null.html
+[SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/on-send.html
<!--- INDEX kotlinx.coroutines.experimental.selects -->
[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/select.html
-[SelectBuilder.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-receive.html
-[SelectBuilder.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-receive-or-null.html
-[SelectBuilder.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-send.html
-[SelectBuilder.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-await.html
<!--- END -->
diff --git a/integration/kotlinx-coroutines-guava/pom.xml b/integration/kotlinx-coroutines-guava/pom.xml
index 3f117a5..c798597 100644
--- a/integration/kotlinx-coroutines-guava/pom.xml
+++ b/integration/kotlinx-coroutines-guava/pom.xml
@@ -21,13 +21,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-guava</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>integration</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -41,7 +45,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
<link>
<url>https://google.github.io/guava/releases/18.0/api/docs/</url>
diff --git a/integration/kotlinx-coroutines-guava/src/main/kotlin/kotlinx/coroutines/experimental/guava/ListenableFuture.kt b/integration/kotlinx-coroutines-guava/src/main/kotlin/kotlinx/coroutines/experimental/guava/ListenableFuture.kt
index e10ead1..d458aeb 100644
--- a/integration/kotlinx-coroutines-guava/src/main/kotlin/kotlinx/coroutines/experimental/guava/ListenableFuture.kt
+++ b/integration/kotlinx-coroutines-guava/src/main/kotlin/kotlinx/coroutines/experimental/guava/ListenableFuture.kt
@@ -22,6 +22,7 @@
import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.experimental.*
import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
/**
@@ -29,11 +30,12 @@
* This coroutine builder uses [CommonPool] context by default.
*
* The running coroutine is cancelled when the resulting future is cancelled or otherwise completed.
- * If the [context] for the new coroutine is omitted or is explicitly specified but does not include a
- * coroutine interceptor, then [CommonPool] is used.
- * See [CoroutineDispatcher] for other standard [context] implementations that are provided by `kotlinx.coroutines`.
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
*
* By default, the coroutine is immediately scheduled for execution.
* Other options can be specified via `start` parameter. See [CoroutineStart] for details.
@@ -43,17 +45,17 @@
*
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
*
- * @param context context of the coroutine
- * @param start coroutine start option
- * @param block the coroutine code
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the coroutine code.
*/
public fun <T> future(
- context: CoroutineContext = CommonPool,
+ context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): ListenableFuture<T> {
require(!start.isLazy) { "$start start is not supported" }
- val newContext = newCoroutineContext(CommonPool + context)
+ val newContext = newCoroutineContext(context)
val job = Job(newContext[Job])
val future = ListenableFutureCoroutine<T>(newContext + job)
job.cancelFutureOnCompletion(future)
diff --git a/integration/kotlinx-coroutines-jdk8/pom.xml b/integration/kotlinx-coroutines-jdk8/pom.xml
index c3287fc..61dd79a 100644
--- a/integration/kotlinx-coroutines-jdk8/pom.xml
+++ b/integration/kotlinx-coroutines-jdk8/pom.xml
@@ -21,13 +21,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-jdk8</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>integration</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -41,7 +45,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
</externalDocumentationLinks>
<skip>false</skip>
diff --git a/integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/future/Future.kt b/integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/future/Future.kt
index ef13de6..86bd14a 100644
--- a/integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/future/Future.kt
+++ b/integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/future/Future.kt
@@ -22,6 +22,7 @@
import java.util.concurrent.ExecutionException
import java.util.function.BiConsumer
import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.suspendCoroutine
@@ -30,11 +31,12 @@
* This coroutine builder uses [CommonPool] context by default and is conceptually similar to [CompletableFuture.supplyAsync].
*
* The running coroutine is cancelled when the resulting future is cancelled or otherwise completed.
- * If the [context] for the new coroutine is omitted or is explicitly specified but does not include a
- * coroutine interceptor, then [CommonPool] is used.
- * See [CoroutineDispatcher] for other standard [context] implementations that are provided by `kotlinx.coroutines`.
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
*
* By default, the coroutine is immediately scheduled for execution.
* Other options can be specified via `start` parameter. See [CoroutineStart] for details.
@@ -44,17 +46,17 @@
*
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
*
- * @param context context of the coroutine
- * @param start coroutine start option
- * @param block the coroutine code
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param block the coroutine code.
*/
public fun <T> future(
- context: CoroutineContext = CommonPool,
+ context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): CompletableFuture<T> {
require(!start.isLazy) { "$start start is not supported" }
- val newContext = newCoroutineContext(CommonPool + context)
+ val newContext = newCoroutineContext(context)
val job = Job(newContext[Job])
val future = CompletableFutureCoroutine<T>(newContext + job)
job.cancelFutureOnCompletion(future)
diff --git a/integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/time/Time.kt b/integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/time/Time.kt
index 4c8435e..7dd23ba 100644
--- a/integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/time/Time.kt
+++ b/integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/time/Time.kt
@@ -15,6 +15,7 @@
*/
package kotlinx.coroutines.experimental.time
+import kotlinx.coroutines.experimental.CoroutineScope
import kotlinx.coroutines.experimental.selects.SelectBuilder
import java.time.Duration
import java.util.concurrent.TimeUnit
@@ -34,11 +35,25 @@
/**
* "java.time" adapter method for [kotlinx.coroutines.experimental.withTimeout]
*/
-public suspend fun <T> withTimeout(duration: Duration, block: suspend () -> T): T =
+public suspend fun <T> withTimeout(duration: Duration, block: suspend CoroutineScope.() -> T): T =
kotlinx.coroutines.experimental.withTimeout(duration.toNanos(), TimeUnit.NANOSECONDS, block)
/**
+ * @suppress **Deprecated**: for binary compatibility only
+ */
+@Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+public suspend fun <T> withTimeout(duration: Duration, block: suspend () -> T): T =
+ kotlinx.coroutines.experimental.withTimeout(duration.toNanos(), TimeUnit.NANOSECONDS) { block() }
+
+/**
* "java.time" adapter method for [kotlinx.coroutines.experimental.withTimeoutOrNull]
*/
-public suspend fun <T> withTimeoutOrNull(duration: Duration, block: suspend () -> T): T? =
+public suspend fun <T> withTimeoutOrNull(duration: Duration, block: suspend CoroutineScope.() -> T): T? =
kotlinx.coroutines.experimental.withTimeoutOrNull(duration.toNanos(), TimeUnit.NANOSECONDS, block)
+
+/**
+ * @suppress **Deprecated**: for binary compatibility only
+ */
+@Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
+public suspend fun <T> withTimeoutOrNull(duration: Duration, block: suspend () -> T): T? =
+ kotlinx.coroutines.experimental.withTimeoutOrNull(duration.toNanos(), TimeUnit.NANOSECONDS) { block() }
diff --git a/integration/kotlinx-coroutines-nio/pom.xml b/integration/kotlinx-coroutines-nio/pom.xml
index 94ac22d..40e0992 100644
--- a/integration/kotlinx-coroutines-nio/pom.xml
+++ b/integration/kotlinx-coroutines-nio/pom.xml
@@ -22,13 +22,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-nio</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>integration</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -42,7 +46,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
</externalDocumentationLinks>
<skip>false</skip>
diff --git a/integration/kotlinx-coroutines-quasar/pom.xml b/integration/kotlinx-coroutines-quasar/pom.xml
index 93cd255..c8e66ae 100644
--- a/integration/kotlinx-coroutines-quasar/pom.xml
+++ b/integration/kotlinx-coroutines-quasar/pom.xml
@@ -21,13 +21,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-quasar</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>integration</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -41,7 +45,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
<link>
<url>https://google.github.io/guava/releases/18.0/api/docs/</url>
diff --git a/knit/pom.xml b/knit/pom.xml
index c510769..ffeecdc 100644
--- a/knit/pom.xml
+++ b/knit/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
</parent>
<artifactId>knit</artifactId>
diff --git a/knit/resources/knit.properties b/knit/resources/knit.properties
index 141143c..1b3f48a 100644
--- a/knit/resources/knit.properties
+++ b/knit/resources/knit.properties
@@ -16,6 +16,6 @@
site.root=https://kotlin.github.io/kotlinx.coroutines
-module.roots=. integration reactive ui
+module.roots=core integration reactive ui
module.marker=pom.xml
module.docs=target/dokka
diff --git a/knit/src/Knit.kt b/knit/src/Knit.kt
index 2ddabcc..566c80e 100644
--- a/knit/src/Knit.kt
+++ b/knit/src/Knit.kt
@@ -63,7 +63,7 @@
const val LINES_START_UNORDERED_PREDICATE = "LINES_START_UNORDERED"
const val LINES_START_PREDICATE = "LINES_START"
-val API_REF_REGEX = Regex("(^|[ \\]])\\[([A-Za-z0-9_.]+)\\]($|[^\\[\\(])")
+val API_REF_REGEX = Regex("(^|[ \\]])\\[([A-Za-z0-9_().]+)\\]($|[^\\[\\(])")
fun main(args: Array<String>) {
if (args.isEmpty()) {
@@ -424,6 +424,22 @@
val REF_LINE_REGEX = Regex("<a href=\"([a-z/.\\-]+)\">([a-zA-z.]+)</a>")
val INDEX_HTML = "/index.html"
val INDEX_MD = "/index.md"
+val FUNCTIONS_SECTION_HEADER = "### Functions"
+
+val AMBIGUOUS = "#AMBIGUOUS: "
+
+fun HashMap<String,String>.putUnambiguous(key: String, value: String) {
+ val oldValue = this[key]
+ val putVal =
+ if (oldValue != null && oldValue != value) {
+ when {
+ oldValue.contains("[$value]") -> oldValue
+ oldValue.startsWith(AMBIGUOUS) -> "$oldValue; [$value]"
+ else -> "$AMBIGUOUS[$oldValue]; [$value]"
+ }
+ } else value
+ put(key, putVal)
+}
fun loadApiIndex(
docsRoot: String,
@@ -434,18 +450,28 @@
val fileName = docsRoot + "/" + path + INDEX_MD
val visited = mutableSetOf<String>()
val map = HashMap<String,String>()
+ var inFunctionsSection = false
File(fileName).withLineNumberReader<LineNumberReader>(::LineNumberReader) {
while (true) {
val line = readLine() ?: break
+ if (line == FUNCTIONS_SECTION_HEADER) inFunctionsSection = true
val result = REF_LINE_REGEX.matchEntire(line) ?: continue
- val refLink = result.groups[1]!!.value
- if (refLink.startsWith("..")) continue // ignore cross-references
- val refName = namePrefix + result.groups[2]!!.value
- map.putIfAbsent(refName, path + "/" + refLink)
- map.putIfAbsent(pkg + "." + refName, path + "/" + refLink)
- if (refLink.endsWith(INDEX_HTML)) {
- if (visited.add(refLink)) {
- val path2 = path + "/" + refLink.substring(0, refLink.length - INDEX_HTML.length)
+ val link = result.groups[1]!!.value
+ if (link.startsWith("..")) continue // ignore cross-references
+ val absLink = path + "/" + link
+ var name = result.groups[2]!!.value
+ // a special disambiguation fix for pseudo-constructor functions
+ if (inFunctionsSection && name[0] in 'A'..'Z') name += "()"
+ val refName = namePrefix + name
+ val fqName = pkg + "." + refName
+ // Put short names for extensions on 3rd party classes (prefix is FQname of those classes)
+ if (namePrefix != "" && namePrefix[0] in 'a'..'z') map.putUnambiguous(name, absLink)
+ // Always put fully qualified names
+ map.putUnambiguous(refName, absLink)
+ map.putUnambiguous(fqName, absLink)
+ if (link.endsWith(INDEX_HTML)) {
+ if (visited.add(link)) {
+ val path2 = path + "/" + link.substring(0, link.length - INDEX_HTML.length)
map += loadApiIndex(docsRoot, path2, pkg, refName + ".")
?: throw IllegalArgumentException("Failed to parse ${docsRoot + "/" + path2}")
}
@@ -473,6 +499,10 @@
while (it.hasNext()) {
val refName = it.next()
val refLink = map[refName] ?: continue
+ if (refLink.startsWith(AMBIGUOUS)) {
+ println("WARNING: Ambiguous reference to [$refName]: ${refLink.substring(AMBIGUOUS.length)}")
+ continue
+ }
indexList += "[$refName]: $siteRoot/$refLink"
it.remove()
}
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt
deleted file mode 100644
index b85fd80..0000000
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2016-2017 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlinx.coroutines.experimental
-
-import kotlin.coroutines.experimental.Continuation
-import kotlin.coroutines.experimental.CoroutineContext
-
-/**
- * Abstract class to simplify writing of coroutine completion objects that
- * implement completion [Continuation], [Job], and [CoroutineScope] interfaces.
- * It stores the result of continuation in the state of the job.
- *
- * @param active when `true` coroutine is created in _active_ state, when `false` in _new_ state. See [Job] for details.
- * @suppress **This is unstable API and it is subject to change.**
- */
-public abstract class AbstractCoroutine<in T>(
- private val parentContext: CoroutineContext,
- active: Boolean
-) : JobSupport(active), Continuation<T>, CoroutineScope {
- @Suppress("LeakingThis")
- public final override val context: CoroutineContext = parentContext + this
- public final override val coroutineContext: CoroutineContext get() = context
-
- final override val hasCancellingState: Boolean get() = true
-
- final override fun resume(value: T) {
- loopOnState { state ->
- when (state) {
- is Incomplete -> if (updateState(state, value, MODE_ATOMIC_DEFAULT)) return
- is Cancelled -> return // ignore resumes on cancelled continuation
- else -> error("Already resumed, but got value $value")
- }
- }
- }
-
- final override fun resumeWithException(exception: Throwable) {
- loopOnState { state ->
- when (state) {
- is Incomplete -> {
- if (updateState(state, CompletedExceptionally(exception), MODE_ATOMIC_DEFAULT)) return
- }
- is Cancelled -> {
- // ignore resumes on cancelled continuation, but handle exception if a different one is here
- if (exception !== state.exception) handleCoroutineException(context, exception)
- return
- }
- else -> throw IllegalStateException("Already resumed, but got exception $exception", exception)
- }
- }
- }
-
- final override fun handleException(exception: Throwable) {
- handleCoroutineException(parentContext, exception)
- }
-}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt
deleted file mode 100644
index 1baa7fe..0000000
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt
+++ /dev/null
@@ -1,1101 +0,0 @@
-/*
- * Copyright 2016-2017 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlinx.coroutines.experimental
-
-import kotlinx.atomicfu.atomic
-import kotlinx.atomicfu.loop
-import kotlinx.coroutines.experimental.internal.LockFreeLinkedListHead
-import kotlinx.coroutines.experimental.internal.LockFreeLinkedListNode
-import kotlinx.coroutines.experimental.internal.OpDescriptor
-import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
-import kotlinx.coroutines.experimental.selects.SelectBuilder
-import kotlinx.coroutines.experimental.selects.SelectInstance
-import kotlinx.coroutines.experimental.selects.select
-import java.util.concurrent.Future
-import kotlin.coroutines.experimental.Continuation
-import kotlin.coroutines.experimental.CoroutineContext
-
-// --------------- core job interfaces ---------------
-
-/**
- * A background job. Conceptually, a job is a cancellable thing with a simple life-cycle that
- * culminates in its completion. Jobs can be arranged into parent-child hierarchies where cancellation
- * or completion of parent immediately cancels all its children.
- *
- * The most basic instances of [Job] are created with [launch] coroutine builder or with a
- * `Job()` factory function. Other coroutine builders and primitives like
- * [Deferred] also implement [Job] interface.
- *
- * A job has the following states:
- *
- * | **State** | [isActive] | [isCompleted] | [isCancelled] |
- * | --------------------------------------- | ---------- | ------------- | ------------- |
- * | _New_ (optional initial state) | `false` | `false` | `false` |
- * | _Active_ (default initial state) | `true` | `false` | `false` |
- * | _Cancelling_ (optional transient state) | `false` | `false` | `true` |
- * | _Cancelled_ (final state) | `false` | `true` | `true` |
- * | _Completed normally_ (final state) | `false` | `true` | `false` |
- *
- * Usually, a job is created in _active_ state (it is created and started). However, coroutine builders
- * that provide an optional `start` parameter create a coroutine in _new_ state when this parameter is set to
- * [CoroutineStart.LAZY]. Such a job can be made _active_ by invoking [start] or [join].
- *
- * A job can be _cancelled_ at any time with [cancel] function that forces it to transition to
- * _cancelling_ state immediately. Simple jobs, that are not backed by a coroutine, like
- * [CompletableDeferred] and the result of `Job()` factory function, don't
- * have a _cancelling_ state, but become _cancelled_ on [cancel] immediately.
- * Coroutines, on the other hand, become _cancelled_ only when they finish executing their code.
- *
- * ```
- * +-----+ start +--------+ complete +-----------+
- * | New | ---------------> | Active | -----------> | Completed |
- * +-----+ +--------+ | normally |
- * | | +-----------+
- * | cancel | cancel
- * V V
- * +-----------+ finish +------------+
- * | Cancelled | <--------- | Cancelling |
- * |(completed)| +------------+
- * +-----------+
- * ```
- *
- * A job in the coroutine [context][CoroutineScope.context] represents the coroutine itself.
- * A job is active while the coroutine is working and job's cancellation aborts the coroutine when
- * the coroutine is suspended on a _cancellable_ suspension point by throwing [CancellationException]
- * or the cancellation cause inside the coroutine.
- *
- * A job can have a _parent_ job. A job with a parent is cancelled when its parent is cancelled or completes.
- *
- * All functions on this interface and on all interfaces derived from it are **thread-safe** and can
- * be safely invoked from concurrent coroutines without external synchronization.
- */
-public interface Job : CoroutineContext.Element {
- /**
- * Key for [Job] instance in the coroutine context.
- */
- public companion object Key : CoroutineContext.Key<Job> {
- /**
- * Creates a new job object in _active_ state.
- * It is optionally a child of a [parent] job.
- * @suppress **Deprecated**
- */
- @Deprecated("Replaced with top-level function", level = DeprecationLevel.HIDDEN)
- public operator fun invoke(parent: Job? = null): Job = Job(parent)
-
- init {
- /*
- * Here we make sure that CoroutineExceptionHandler is always initialized in advance, so
- * that if a coroutine fails due to StackOverflowError we don't fail to report this error
- * trying to initialize CoroutineExceptionHandler
- */
- CoroutineExceptionHandler
- }
- }
-
- // ------------ state query ------------
-
- /**
- * Returns `true` when this job is active -- it was already started and has not completed or cancelled yet.
- */
- public val isActive: Boolean
-
- /**
- * Returns `true` when this job has completed for any reason. A job that was cancelled and has
- * finished its execution is also considered complete.
- */
- public val isCompleted: Boolean
-
- /**
- * Returns `true` if this job was [cancelled][cancel]. In the general case, it does not imply that the
- * job has already [completed][isCompleted] (it may still be cancelling whatever it was doing).
- */
- public val isCancelled: Boolean
-
- /**
- * Returns the exception that signals the completion of this job -- it returns the original
- * [cancel] cause or an instance of [CancellationException] if this job had completed
- * normally or was cancelled without a cause. This function throws
- * [IllegalStateException] when invoked for an job that has not [completed][isCompleted] nor
- * [isCancelled] yet.
- *
- * The [cancellable][suspendCancellableCoroutine] suspending functions throw this exception
- * when trying to suspend in the context of this job.
- */
- public fun getCompletionException(): Throwable
-
- // ------------ state update ------------
-
- /**
- * Starts coroutine related to this job (if any) if it was not started yet.
- * The result `true` if this invocation actually started coroutine or `false`
- * if it was already started or completed.
- */
- public fun start(): Boolean
-
- /**
- * Cancel this job with an optional cancellation [cause]. The result is `true` if this job was
- * cancelled as a result of this invocation and `false` otherwise
- * (if it was already _completed_ or if it is [NonCancellable]).
- * Repeated invocations of this function have no effect and always produce `false`.
- *
- * When cancellation has a clear reason in the code, an instance of [CancellationException] should be created
- * at the corresponding original cancellation site and passed into this method to aid in debugging by providing
- * both the context of cancellation and text description of the reason.
- */
- public fun cancel(cause: Throwable? = null): Boolean
-
- // ------------ state waiting ------------
-
- /**
- * Suspends coroutine until this job is complete. This invocation resumes normally (without exception)
- * when the job is complete for any reason. This function also [starts][Job.start] the corresponding coroutine
- * if the [Job] was still in _new_ state.
- *
- * This suspending function is cancellable. If the [Job] of the invoking coroutine is cancelled or completed while this
- * suspending function is suspended, this function immediately resumes with [CancellationException].
- *
- * This function can be used in [select] invocation with [onJoin][SelectBuilder.onJoin] clause.
- * Use [isCompleted] to check for completion of this job without waiting.
- */
- public suspend fun join()
-
- // ------------ low-level state-notification ------------
-
- /**
- * Registers handler that is **synchronously** invoked once on completion of this job.
- * When job is already complete, then the handler is immediately invoked
- * with a job's cancellation cause or `null`. Otherwise, handler will be invoked once when this
- * job is complete.
- *
- * The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] the
- * registration of this handler and release its memory if its invocation is no longer needed.
- * There is no need to dispose the handler after completion of this job. The references to
- * all the handlers are released when this job completes.
- *
- * Note, that the handler is not invoked on invocation of [cancel] when
- * job becomes _cancelling_, but only when the corresponding coroutine had finished execution
- * of its code and became _cancelled_. There is an overloaded version of this function
- * with `onCancelling` parameter to receive notification on _cancelling_ state.
- *
- * **Note**: This function is a part of internal machinery that supports parent-child hierarchies
- * and allows for implementation of suspending functions that wait on the Job's state.
- * This function should not be used in general application code.
- * Implementations of `CompletionHandler` must be fast and _lock-free_.
- */
- public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
-
- /**
- * Registers handler that is **synchronously** invoked once on cancellation or completion of this job.
- * When job is already complete, then the handler is immediately invoked
- * with a job's cancellation cause or `null`. Otherwise, handler will be invoked once when this
- * job is cancelled or complete.
- *
- * Invocation of this handler on a transition to a transient _cancelling_ state
- * is controlled by [onCancelling] boolean parameter.
- * The handler is invoked on invocation of [cancel] when
- * job becomes _cancelling_ when [onCancelling] parameters is set to `true`. However,
- * when this [Job] is not backed by a coroutine, like [CompletableDeferred] or [CancellableContinuation]
- * (both of which do not posses a _cancelling_ state), then the value of [onCancelling] parameter is ignored.
- *
- * The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] the
- * registration of this handler and release its memory if its invocation is no longer needed.
- * There is no need to dispose the handler after completion of this job. The references to
- * all the handlers are released when this job completes.
- *
- * **Note**: This function is a part of internal machinery that supports parent-child hierarchies
- * and allows for implementation of suspending functions that wait on the Job's state.
- * This function should not be used in general application code.
- * Implementations of `CompletionHandler` must be fast and _lock-free_.
- */
- public fun invokeOnCompletion(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle
-
- // ------------ unstable internal API ------------
-
- /**
- * Registers [onJoin][SelectBuilder.onJoin] select clause.
- * @suppress **This is unstable API and it is subject to change.**
- */
- public fun <R> registerSelectJoin(select: SelectInstance<R>, block: suspend () -> R)
-
- /**
- * @suppress **Error**: Operator '+' on two Job objects is meaningless.
- * Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts.
- * The job to the right of `+` just replaces the job the left of `+`.
- */
- @Suppress("DeprecatedCallableAddReplaceWith")
- @Deprecated(message = "Operator '+' on two Job objects is meaningless. " +
- "Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts. " +
- "The job to the right of `+` just replaces the job the left of `+`.",
- level = DeprecationLevel.ERROR)
- public operator fun plus(other: Job) = other
-
- /**
- * Registration object for [invokeOnCompletion]. It can be used to [unregister] if needed.
- * There is no need to unregister after completion.
- * @suppress **Deprecated**: Replace with `DisposableHandle`
- */
- @Deprecated(message = "Replace with `DisposableHandle`",
- replaceWith = ReplaceWith("DisposableHandle"))
- public interface Registration {
- /**
- * Unregisters completion handler.
- * @suppress **Deprecated**: Replace with `dispose`
- */
- @Deprecated(message = "Replace with `dispose`",
- replaceWith = ReplaceWith("dispose()"))
- public fun unregister()
- }
-}
-
-/**
- * Creates a new job object in an _active_ state.
- * It is optionally a child of a [parent] job.
- */
-public fun Job(parent: Job? = null): Job = JobImpl(parent)
-
-/**
- * A handle to an allocated object that can be disposed to make it eligible for garbage collection.
- */
-@Suppress("DEPRECATION") // todo: remove when Job.Registration is removed
-public interface DisposableHandle : Job.Registration {
- /**
- * Disposes the corresponding object, making it eligible for garbage collection.
- * Repeated invocation of this function has no effect.
- */
- public fun dispose()
-
- /**
- * Unregisters completion handler.
- * @suppress **Deprecated**: Replace with `dispose`
- */
- @Deprecated(message = "Replace with `dispose`",
- replaceWith = ReplaceWith("dispose()"))
- public override fun unregister() = dispose()
-}
-
-/**
- * Handler for [Job.invokeOnCompletion].
- *
- * **Note**: This type is a part of internal machinery that supports parent-child hierarchies
- * and allows for implementation of suspending functions that wait on the Job's state.
- * This type should not be used in general application code.
- * Implementations of `CompletionHandler` must be fast and _lock-free_.
- */
-public typealias CompletionHandler = (Throwable?) -> Unit
-
-/**
- * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending.
- */
-public typealias CancellationException = java.util.concurrent.CancellationException
-
-/**
- * Unregisters a specified [registration] when this job is complete.
- *
- * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
- * ```
- * invokeOnCompletion { registration.unregister() }
- * ```
- * @suppress: **Deprecated**: Renamed to `disposeOnCompletion`.
- */
-@Deprecated(message = "Renamed to `disposeOnCompletion`",
- replaceWith = ReplaceWith("disposeOnCompletion(registration)"))
-public fun Job.unregisterOnCompletion(registration: DisposableHandle): DisposableHandle =
- invokeOnCompletion(DisposeOnCompletion(this, registration))
-
-/**
- * Disposes a specified [handle] when this job is complete.
- *
- * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
- * ```
- * invokeOnCompletion { handle.dispose() }
- * ```
- */
-public fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle =
- invokeOnCompletion(DisposeOnCompletion(this, handle))
-
-/**
- * Cancels a specified [future] when this job is complete.
- *
- * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
- * ```
- * invokeOnCompletion { future.cancel(false) }
- * ```
- */
-public fun Job.cancelFutureOnCompletion(future: Future<*>): DisposableHandle =
- invokeOnCompletion(CancelFutureOnCompletion(this, future))
-
-/**
- * @suppress **Deprecated**: `join` is now a member function of `Job`.
- */
-@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "DeprecatedCallableAddReplaceWith")
-@Deprecated(message = "`join` is now a member function of `Job`")
-public suspend fun Job.join() = this.join()
-
-/**
- * No-op implementation of [Job.Registration].
- */
-@Deprecated(message = "Replace with `NonDisposableHandle`",
- replaceWith = ReplaceWith("NonDisposableHandle"))
-typealias EmptyRegistration = NonDisposableHandle
-
-/**
- * No-op implementation of [DisposableHandle].
- */
-public object NonDisposableHandle : DisposableHandle {
- /** Does not do anything. */
- override fun dispose() {}
-
- /** Returns "NonDisposableHandle" string. */
- override fun toString(): String = "NonDisposableHandle"
-}
-
-// --------------- utility classes to simplify job implementation
-
-/**
- * A concrete implementation of [Job]. It is optionally a child to a parent job.
- * This job is cancelled when the parent is complete, but not vise-versa.
- *
- * This is an open class designed for extension by more specific classes that might augment the
- * state and mare store addition state information for completed jobs, like their result values.
- *
- * @param active when `true` the job is created in _active_ state, when `false` in _new_ state. See [Job] for details.
- * @suppress **This is unstable API and it is subject to change.**
- */
-public open class JobSupport(active: Boolean) : Job {
- override val key: CoroutineContext.Key<*> get() = Job
-
- /*
- === Internal states ===
-
- name state class public state description
- ------ ------------ ------------ -----------
- EMPTY_N EmptyNew : New no listeners
- EMPTY_A EmptyActive : Active no listeners
- SINGLE JobNode : Active a single listener
- SINGLE+ JobNode : Active a single listener + NodeList added as its next
- LIST_N NodeList : New a list of listeners (promoted once, does not got back to EmptyNew)
- LIST_A NodeList : Active a list of listeners (promoted once, does not got back to JobNode/EmptyActive)
- CANCELLING Cancelling : Cancelling(*) a list of listeners (promoted once)
- FINAL_C Cancelled : Cancelled cancelled (final state)
- FINAL_F Failed : Completed failed for other reason (final state)
- FINAL_R <any> : Completed produced some result
-
- === Transitions ===
-
- New states Active states Inactive states
- +---------+ +---------+ +----------+
- | EMPTY_N | --+-> | EMPTY_A | --+-> | FINAL_* |
- +---------+ | +---------+ | +----------+
- | | | ^ |
- | | V | |
- | | +---------+ |
- | | | SINGLE | --+
- | | +---------+ |
- | | | |
- | | V |
- | | +---------+ |
- | +-- | SINGLE+ | --+
- | +---------+ |
- | | |
- V V |
- +---------+ +---------+ |
- | LIST_N | ----> | LIST_A | --+
- +---------+ +---------+ |
- | | |
- | V |
- | +------------+ |
- +-------> | CANCELLING | --+
- +------------+
-
- This state machine and its transition matrix are optimized for the common case when job is created in active
- state (EMPTY_A) and at most one completion listener is added to it during its life-time.
-
- Note, that the actual `_state` variable can also be a reference to atomic operation descriptor `OpDescriptor`
-
- (*) The CANCELLING state is used only in AbstractCoroutine class. A general Job (that does not
- extend AbstractCoroutine) does not have CANCELLING state. It immediately transitions to
- FINAL_C (Cancelled) state on cancellation/completion
- */
-
- // Note: use shared objects while we have no listeners
- private val _state = atomic<Any?>(if (active) EmptyActive else EmptyNew)
-
- @Volatile
- private var parentHandle: DisposableHandle? = null
-
- // ------------ initialization ------------
-
- /**
- * Initializes parent job.
- * It shall be invoked at most once after construction after all other initialization.
- */
- public fun initParentJob(parent: Job?) {
- check(parentHandle == null)
- if (parent == null) {
- parentHandle = NonDisposableHandle
- return
- }
- parent.start() // make sure the parent is started
- // directly pass HandlerNode to parent scope to optimize one closure object (see makeNode)
- val newRegistration = parent.invokeOnCompletion(ParentOnCancellation(parent), onCancelling = true)
- parentHandle = newRegistration
- // now check our state _after_ registering (see updateState order of actions)
- if (isCompleted) newRegistration.dispose()
- }
-
- private inner class ParentOnCancellation(parent: Job) : JobCancellationNode<Job>(parent) {
- override fun invokeOnce(reason: Throwable?) { onParentCancellation(reason) }
- override fun toString(): String = "ParentOnCancellation[${this@JobSupport}]"
- }
-
- /**
- * Invoked at most once on parent completion.
- * @suppress **This is unstable API and it is subject to change.**
- */
- protected open fun onParentCancellation(cause: Throwable?) {
- // if parent was completed with CancellationException then use it as the cause of our cancellation, too.
- // however, we shall not use application specific exceptions here. So if parent crashes due to IOException,
- // we cannot and should not cancel the child with IOException
- cancel(cause as? CancellationException)
- }
-
- // ------------ state query ------------
-
- /**
- * Returns current state of this job.
- */
- protected val state: Any? get() {
- _state.loop { state -> // helper loop on state (complete in-progress atomic operations)
- if (state !is OpDescriptor) return state
- state.perform(this)
- }
- }
-
- protected inline fun loopOnState(block: (Any?) -> Unit): Nothing {
- while (true) {
- block(state)
- }
- }
-
- public final override val isActive: Boolean get() {
- val state = this.state
- return state is Incomplete && state.isActive
- }
-
- public final override val isCompleted: Boolean get() = state !is Incomplete
-
- public final override val isCancelled: Boolean get() {
- val state = this.state
- return state is Cancelled || state is Cancelling
- }
-
- // ------------ state update ------------
-
- /**
- * Updates current [state] of this job.
- */
- protected fun updateState(expect: Any, proposedUpdate: Any?, mode: Int): Boolean {
- val update = coerceProposedUpdate(expect, proposedUpdate)
- if (!tryUpdateState(expect, update)) return false
- completeUpdateState(expect, update, mode)
- // if an exceptional completion was suppressed (because cancellation was in progress), then report it separately
- if (proposedUpdate !== update && proposedUpdate is CompletedExceptionally && proposedUpdate.cause != null)
- handleException(proposedUpdate.cause)
- return true
- }
-
- // when Job is in Cancelling state, it can only be promoted to Cancelled state with the same cause
- // however, null cause can be replaced with more specific CancellationException (that contains stack trace)
- private fun coerceProposedUpdate(expect: Any, proposedUpdate: Any?): Any? =
- if (expect is Cancelling && !correspondinglyCancelled(expect, proposedUpdate))
- expect.cancelled else proposedUpdate
-
- private fun correspondinglyCancelled(expect: Cancelling, proposedUpdate: Any?): Boolean {
- if (proposedUpdate !is Cancelled) return false
- return proposedUpdate.cause === expect.cancelled.cause ||
- proposedUpdate.cause is CancellationException && expect.cancelled.cause == null
- }
-
- /**
- * Tries to initiate update of the current [state] of this job.
- */
- protected fun tryUpdateState(expect: Any, update: Any?): Boolean {
- require(expect is Incomplete && update !is Incomplete) // only incomplete -> completed transition is allowed
- if (!_state.compareAndSet(expect, update)) return false
- // Unregister from parent job
- parentHandle?.dispose() // volatile read parentHandle _after_ state was updated
- return true // continues in completeUpdateState
- }
-
- /**
- * Completes update of the current [state] of this job.
- */
- protected fun completeUpdateState(expect: Any, update: Any?, mode: Int) {
- // Invoke completion handlers
- val cause = (update as? CompletedExceptionally)?.cause
- when (expect) {
- is JobNode<*> -> try { // SINGLE/SINGLE+ state -- one completion handler (common case)
- expect.invoke(cause)
- } catch (ex: Throwable) {
- handleException(ex)
- }
- is NodeList -> notifyCompletion(expect, cause) // LIST state -- a list of completion handlers
- is Cancelling -> notifyCompletion(expect.list, cause) // has list, too
- // otherwise -- do nothing (it was Empty*)
- else -> check(expect is Empty)
- }
- // Do overridable processing after completion handlers
- if (expect !is Cancelling) onCancellation() // only notify when was not cancelling before
- afterCompletion(update, mode)
- }
-
- private inline fun <reified T: JobNode<*>> notifyHandlers(list: NodeList, cause: Throwable?) {
- var exception: Throwable? = null
- list.forEach<T> { node ->
- try {
- node.invoke(cause)
- } catch (ex: Throwable) {
- exception?.apply { addSuppressed(ex) } ?: run { exception = ex }
- }
-
- }
- exception?.let { handleException(it) }
- }
-
- private fun notifyCompletion(list: NodeList, cause: Throwable?) =
- notifyHandlers<JobNode<*>>(list, cause)
-
- private fun notifyCancellation(list: NodeList, cause: Throwable?) =
- notifyHandlers<JobCancellationNode<*>>(list, cause)
-
- public final override fun start(): Boolean {
- loopOnState { state ->
- when (startInternal(state)) {
- FALSE -> return false
- TRUE -> return true
- }
- }
- }
-
- // returns: RETRY/FALSE/TRUE:
- // FALSE when not new,
- // TRUE when started
- // RETRY when need to retry
- private fun startInternal(state: Any?): Int {
- when (state) {
- is Empty -> { // EMPTY_X state -- no completion handlers
- if (state.isActive) return FALSE // already active
- if (!_state.compareAndSet(state, EmptyActive)) return RETRY
- onStart()
- return TRUE
- }
- is NodeList -> { // LIST -- a list of completion handlers (either new or active)
- if (state._active.value != 0) return FALSE
- if (!state._active.compareAndSet(0, 1)) return RETRY
- onStart()
- return TRUE
- }
- else -> return FALSE // not a new state
- }
- }
-
- /**
- * Override to provide the actual [start] action.
- */
- protected open fun onStart() {}
-
- public final override fun getCompletionException(): Throwable {
- val state = this.state
- return when (state) {
- is Cancelling -> state.cancelled.exception
- is Incomplete -> error("Job was not completed or cancelled yet")
- is CompletedExceptionally -> state.exception
- else -> CancellationException("Job has completed normally")
- }
- }
-
- /**
- * Returns the cause that signals the completion of this job -- it returns the original
- * [cancel] cause or **`null` if this job had completed
- * normally or was cancelled without a cause**. This function throws
- * [IllegalStateException] when invoked for an job that has not [completed][isCompleted] nor
- * [isCancelled] yet.
- */
- protected fun getCompletionCause(): Throwable? {
- val state = this.state
- return when (state) {
- is Cancelling -> state.cancelled.cause
- is Incomplete -> error("Job was not completed or cancelled yet")
- is CompletedExceptionally -> state.cause
- else -> null
- }
- }
-
- public final override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle =
- installHandler(handler, onCancelling = false)
-
- public final override fun invokeOnCompletion(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle =
- installHandler(handler, onCancelling = onCancelling && hasCancellingState)
-
- private fun installHandler(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle {
- var nodeCache: JobNode<*>? = null
- loopOnState { state ->
- when (state) {
- is Empty -> { // EMPTY_X state -- no completion handlers
- if (state.isActive) {
- // try move to SINGLE state
- val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
- if (_state.compareAndSet(state, node)) return node
- } else
- promoteEmptyToNodeList(state) // that way we can add listener for non-active coroutine
- }
- is JobNode<*> -> { // SINGLE/SINGLE+ state -- one completion handler
- promoteSingleToNodeList(state)
- }
- is NodeList -> { // LIST -- a list of completion handlers (either new or active)
- val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
- if (addLastAtomic(state, state, node)) return node
- }
- is Cancelling -> { // CANCELLING -- has a list of completion handlers
- if (onCancelling) { // installing cancellation handler on job that is being cancelled
- handler((state as? CompletedExceptionally)?.exception)
- return NonDisposableHandle
- }
- val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
- if (addLastAtomic(state, state.list, node)) return node
- }
- else -> { // is inactive
- handler((state as? CompletedExceptionally)?.exception)
- return NonDisposableHandle
- }
- }
- }
- }
-
- private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode<*> =
- if (onCancelling)
- (handler as? JobCancellationNode<*>)?.also { require(it.job === this) }
- ?: InvokeOnCancellation(this, handler)
- else
- (handler as? JobNode<*>)?.also { require(it.job === this && (!hasCancellingState || it !is JobCancellationNode)) }
- ?: InvokeOnCompletion(this, handler)
-
-
- private fun addLastAtomic(expect: Any, list: NodeList, node: JobNode<*>) =
- list.addLastIf(node) { this.state === expect }
-
- private fun promoteEmptyToNodeList(state: Empty) {
- // try to promote it to list in new state
- _state.compareAndSet(state, NodeList(state.isActive))
- }
-
- private fun promoteSingleToNodeList(state: JobNode<*>) {
- // try to promote it to list (SINGLE+ state)
- state.addOneIfEmpty(NodeList(active = true))
- // it must be in SINGLE+ state or state has changed (node could have need removed from state)
- val list = state.next // either NodeList or somebody else won the race, updated state
- // just attempt converting it to list if state is still the same, then we'll continue lock-free loop
- _state.compareAndSet(state, list)
- }
-
- final override suspend fun join() {
- if (!joinInternal()) return // fast-path no wait
- return joinSuspend() // slow-path wait
- }
-
- private fun joinInternal(): Boolean {
- loopOnState { state ->
- if (state !is Incomplete) return false // not active anymore (complete) -- no need to wait
- if (startInternal(state) >= 0) return true // wait unless need to retry
- }
- }
-
- private suspend fun joinSuspend() = suspendCancellableCoroutine<Unit> { cont ->
- cont.disposeOnCompletion(invokeOnCompletion(ResumeOnCompletion(this, cont)))
- }
-
- override fun <R> registerSelectJoin(select: SelectInstance<R>, block: suspend () -> R) {
- // fast-path -- check state and select/return if needed
- loopOnState { state ->
- if (select.isSelected) return
- if (state !is Incomplete) {
- // already complete -- select result
- if (select.trySelect(null))
- block.startCoroutineUndispatched(select.completion)
- return
- }
- if (startInternal(state) == 0) {
- // slow-path -- register waiter for completion
- select.disposeOnSelect(invokeOnCompletion(SelectJoinOnCompletion(this, select, block)))
- return
- }
- }
- }
-
- internal fun removeNode(node: JobNode<*>) {
- // remove logic depends on the state of the job
- loopOnState { state ->
- when (state) {
- is JobNode<*> -> { // SINGE/SINGLE+ state -- one completion handler
- if (state !== node) return // a different job node --> we were already removed
- // try remove and revert back to empty state
- if (_state.compareAndSet(state, EmptyActive)) return
- }
- is NodeList, is Cancelling -> { // LIST or CANCELLING -- a list of completion handlers
- // remove node from the list
- node.remove()
- return
- }
- else -> return // it is inactive or Empty* (does not have any completion handlers)
- }
- }
- }
-
- protected open val hasCancellingState: Boolean get() = false
-
- public final override fun cancel(cause: Throwable?): Boolean =
- if (hasCancellingState)
- makeCancelling(cause) else
- makeCancelled(cause)
-
- // we will be dispatching coroutine to process its cancellation exception, so there is no need for
- // an extra check for Job status in MODE_CANCELLABLE
- private fun updateStateCancelled(state: Incomplete, cause: Throwable?) =
- updateState(state, Cancelled(cause), mode = MODE_ATOMIC_DEFAULT)
-
- // transitions to Cancelled state
- private fun makeCancelled(cause: Throwable?): Boolean {
- loopOnState { state ->
- if (state !is Incomplete) return false // quit if already complete
- if (updateStateCancelled(state, cause)) return true
- }
- }
-
- // transitions to Cancelling state
- private fun makeCancelling(cause: Throwable?): Boolean {
- loopOnState { state ->
- when (state) {
- is Empty -> { // EMPTY_X state -- no completion handlers
- if (state.isActive) {
- promoteEmptyToNodeList(state) // this way can wrap it into Cancelling on next pass
- } else {
- // cancelling a non-started coroutine makes it immediately cancelled
- // (and we have no listeners to notify which makes it very simple)
- if (updateStateCancelled(state, cause)) return true
- }
- }
- is JobNode<*> -> { // SINGLE/SINGLE+ state -- one completion handler
- promoteSingleToNodeList(state)
- }
- is NodeList -> { // LIST -- a list of completion handlers (either new or active)
- if (state.isActive) {
- // try make it cancelling on the condition that we're still in this state
- if (_state.compareAndSet(state, Cancelling(state, Cancelled(cause)))) {
- notifyCancellation(state, cause)
- onCancellation()
- return true
- }
- } else {
- // cancelling a non-started coroutine makes it immediately cancelled
- if (updateStateCancelled(state, cause))
- return true
- }
- }
- else -> { // is inactive or already cancelling
- return false
- }
- }
- }
- }
-
- /**
- * Override to process any exceptions that were encountered while invoking completion handlers
- * installed via [invokeOnCompletion].
- */
- protected open fun handleException(exception: Throwable) {
- throw exception
- }
-
- /**
- * It is invoked once when job is cancelled or is completed, similarly to [invokeOnCompletion] with
- * `onCancelling` set to `true`.
- */
- protected open fun onCancellation() {}
-
- /**
- * Override for post-completion actions that need to do something with the state.
- * @param mode completion mode.
- */
- protected open fun afterCompletion(state: Any?, mode: Int) {}
-
- // for nicer debugging
- override fun toString(): String {
- val state = this.state
- val result = if (state is Incomplete) "" else "[$state]"
- return "${this::class.java.simpleName}{${stateToString(state)}}$result@${Integer.toHexString(System.identityHashCode(this))}"
- }
-
- /**
- * Interface for incomplete [state] of a job.
- */
- public interface Incomplete {
- val isActive: Boolean
- }
-
- private class Cancelling(
- @JvmField val list: NodeList,
- @JvmField val cancelled: Cancelled
- ) : Incomplete {
- override val isActive: Boolean get() = false
- }
-
- private class NodeList(
- active: Boolean
- ) : LockFreeLinkedListHead(), Incomplete {
- val _active = atomic(if (active) 1 else 0)
-
- override val isActive: Boolean get() = _active.value != 0
-
- override fun toString(): String = buildString {
- append("List")
- append(if (isActive) "{Active}" else "{New}")
- append("[")
- var first = true
- this@NodeList.forEach<JobNode<*>> { node ->
- if (first) first = false else append(", ")
- append(node)
- }
- append("]")
- }
- }
-
- /**
- * Class for a [state] of a job that had completed exceptionally, including cancellation.
- *
- * @param cause the exceptional completion cause. If `cause` is null, then a [CancellationException]
- * if created on first get from [exception] property.
- */
- public open class CompletedExceptionally(
- @JvmField val cause: Throwable?
- ) {
- @Volatile
- private var _exception: Throwable? = cause // materialize CancellationException on first need
-
- /**
- * Returns completion exception.
- */
- public val exception: Throwable get() =
- _exception ?: // atomic read volatile var or else create new
- CancellationException("Job was cancelled").also { _exception = it }
-
- override fun toString(): String = "${this::class.java.simpleName}[$exception]"
- }
-
- /**
- * A specific subclass of [CompletedExceptionally] for cancelled jobs.
- */
- public class Cancelled(
- cause: Throwable?
- ) : CompletedExceptionally(cause)
-
-
- /*
- * =================================================================================================
- * This is ready-to-use implementation for Deferred interface.
- * However, it is not type-safe. Conceptually it just exposes the value of the underlying
- * completed state as `Any?`
- * =================================================================================================
- */
-
- public val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally
-
- protected fun getCompletedInternal(): Any? {
- val state = this.state
- check(state !is Incomplete) { "This job has not completed yet" }
- if (state is CompletedExceptionally) throw state.exception
- return state
- }
-
- protected suspend fun awaitInternal(): Any? {
- // fast-path -- check state (avoid extra object creation)
- while(true) { // lock-free loop on state
- val state = this.state
- if (state !is Incomplete) {
- // already complete -- just return result
- if (state is CompletedExceptionally) throw state.exception
- return state
-
- }
- if (startInternal(state) >= 0) break // break unless needs to retry
- }
- return awaitSuspend() // slow-path
- }
-
- private suspend fun awaitSuspend(): Any? = suspendCancellableCoroutine { cont ->
- cont.disposeOnCompletion(invokeOnCompletion {
- val state = this.state
- check(state !is Incomplete)
- if (state is CompletedExceptionally)
- cont.resumeWithException(state.exception)
- else
- cont.resume(state)
- })
- }
-
- protected fun <R> registerSelectAwaitInternal(select: SelectInstance<R>, block: suspend (Any?) -> R) {
- // fast-path -- check state and select/return if needed
- loopOnState { state ->
- if (select.isSelected) return
- if (state !is Incomplete) {
- // already complete -- select result
- if (select.trySelect(null)) {
- if (state is CompletedExceptionally)
- select.resumeSelectCancellableWithException(state.exception)
- else
- block.startCoroutineUndispatched(state, select.completion)
- }
- return
- }
- if (startInternal(state) == 0) {
- // slow-path -- register waiter for completion
- select.disposeOnSelect(invokeOnCompletion(SelectAwaitOnCompletion(this, select, block)))
- return
- }
- }
- }
-
- internal fun <R> selectAwaitCompletion(select: SelectInstance<R>, block: suspend (Any?) -> R) {
- val state = this.state
- // Note: await is non-atomic (can be cancelled while dispatched)
- if (state is CompletedExceptionally)
- select.resumeSelectCancellableWithException(state.exception)
- else
- block.startCoroutineCancellable(state, select.completion)
- }
-}
-
-internal fun stateToString(state: Any?): String =
- if (state is JobSupport.Incomplete)
- if (state.isActive) "Active" else "New"
- else "Completed"
-
-private const val RETRY = -1
-private const val FALSE = 0
-private const val TRUE = 1
-
-private val EmptyNew = Empty(false)
-private val EmptyActive = Empty(true)
-
-private class Empty(override val isActive: Boolean) : JobSupport.Incomplete {
- override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}"
-}
-
-private class JobImpl(parent: Job? = null) : JobSupport(true) {
- init { initParentJob(parent) }
-}
-
-// -------- invokeOnCompletion nodes
-
-internal abstract class JobNode<out J : Job>(
- @JvmField val job: J
-) : LockFreeLinkedListNode(), DisposableHandle, CompletionHandler, JobSupport.Incomplete {
- final override val isActive: Boolean get() = true
- final override fun dispose() = (job as JobSupport).removeNode(this)
- override abstract fun invoke(reason: Throwable?)
-}
-
-private class InvokeOnCompletion(
- job: Job,
- private val handler: CompletionHandler
-) : JobNode<Job>(job) {
- override fun invoke(reason: Throwable?) = handler.invoke(reason)
- override fun toString() = "InvokeOnCompletion[${handler::class.java.name}@${Integer.toHexString(System.identityHashCode(handler))}]"
-}
-
-private class ResumeOnCompletion(
- job: Job,
- private val continuation: Continuation<Unit>
-) : JobNode<Job>(job) {
- override fun invoke(reason: Throwable?) = continuation.resume(Unit)
- override fun toString() = "ResumeOnCompletion[$continuation]"
-}
-
-internal class DisposeOnCompletion(
- job: Job,
- private val handle: DisposableHandle
-) : JobNode<Job>(job) {
- override fun invoke(reason: Throwable?) = handle.dispose()
- override fun toString(): String = "DisposeOnCompletion[$handle]"
-}
-
-private class CancelFutureOnCompletion(
- job: Job,
- private val future: Future<*>
-) : JobNode<Job>(job) {
- override fun invoke(reason: Throwable?) {
- // Don't interrupt when cancelling future on completion, because no one is going to reset this
- // interruption flag and it will cause spurious failures elsewhere
- future.cancel(false)
- }
- override fun toString() = "CancelFutureOnCompletion[$future]"
-}
-
-private class SelectJoinOnCompletion<R>(
- job: JobSupport,
- private val select: SelectInstance<R>,
- private val block: suspend () -> R
-) : JobNode<JobSupport>(job) {
- override fun invoke(reason: Throwable?) {
- if (select.trySelect(null))
- block.startCoroutineCancellable(select.completion)
- }
- override fun toString(): String = "SelectJoinOnCompletion[$select]"
-}
-
-private class SelectAwaitOnCompletion<R>(
- job: JobSupport,
- private val select: SelectInstance<R>,
- private val block: suspend (Any?) -> R
-) : JobNode<JobSupport>(job) {
- override fun invoke(reason: Throwable?) {
- if (select.trySelect(null))
- job.selectAwaitCompletion(select, block)
- }
- override fun toString(): String = "SelectAwaitOnCompletion[$select]"
-}
-
-// -------- invokeOnCancellation nodes
-
-internal abstract class JobCancellationNode<out J : Job>(job: J) : JobNode<J>(job) {
- // shall be invoked at most once, so here is an additional flag
- private val _invoked = atomic(0)
-
- final override fun invoke(reason: Throwable?) {
- if (_invoked.compareAndSet(0, 1)) invokeOnce(reason)
- }
-
- abstract fun invokeOnce(reason: Throwable?)
-}
-
-private class InvokeOnCancellation(
- job: Job,
- private val handler: CompletionHandler
-) : JobCancellationNode<Job>(job) {
- override fun invokeOnce(reason: Throwable?) = handler.invoke(reason)
- override fun toString() = "InvokeOnCancellation[${handler::class.java.name}@${Integer.toHexString(System.identityHashCode(handler))}]"
-}
-
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
deleted file mode 100644
index 0de3192..0000000
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2016-2017 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlinx.coroutines.experimental
-
-import kotlinx.coroutines.experimental.selects.SelectBuilder
-import kotlinx.coroutines.experimental.selects.select
-import java.util.concurrent.TimeUnit
-import kotlin.coroutines.experimental.Continuation
-import kotlin.coroutines.experimental.CoroutineContext
-import kotlin.coroutines.experimental.intrinsics.startCoroutineUninterceptedOrReturn
-import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
-
-/**
- * Runs a given suspending [block] of code inside a coroutine with a specified timeout and throws
- * [TimeoutException] if timeout was exceeded.
- *
- * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
- * cancellable suspending function inside the block throws [TimeoutException], so normally that exception,
- * if uncaught, also gets thrown by `withTimeout` as a result.
- * However, the code in the block can suppress [TimeoutException].
- *
- * The sibling function that does not throw exception on timeout is [withTimeoutOrNull].
- * Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
- *
- * This function delegates to [Delay.invokeOnTimeout] if the context [CoroutineDispatcher]
- * implements [Delay] interface, otherwise it tracks time using a built-in single-threaded scheduled executor service.
- *
- * @param time timeout time
- * @param unit timeout unit (milliseconds by default)
- */
-public suspend fun <T> withTimeout(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend () -> T): T {
- require(time >= 0) { "Timeout time $time cannot be negative" }
- if (time <= 0L) throw CancellationException("Timed out immediately")
- return suspendCoroutineOrReturn { cont: Continuation<T> ->
- val context = cont.context
- val completion = TimeoutCompletion(time, unit, cont)
- // schedule cancellation of this coroutine on time
- completion.disposeOnCompletion(context.delay.invokeOnTimeout(time, unit, completion))
- completion.initParentJob(context[Job])
- // restart block using new coroutine with new job,
- // however start it as undispatched coroutine, because we are already in the proper context
- block.startCoroutineUninterceptedOrReturn(completion)
- }
-}
-
-private open class TimeoutCompletion<U, in T: U>(
- private val time: Long,
- private val unit: TimeUnit,
- @JvmField protected val cont: Continuation<U>
-) : JobSupport(active = true), Runnable, Continuation<T> {
- @Suppress("LeakingThis")
- override val context: CoroutineContext = cont.context + this // mix in this Job into the context
- override fun run() { cancel(TimeoutException(time, unit, this)) }
- override fun resume(value: T) { cont.resumeDirect(value) }
- override fun resumeWithException(exception: Throwable) { cont.resumeDirectWithException(exception) }
-}
-
-/**
- * Runs a given suspending block of code inside a coroutine with a specified timeout and returns
- * `null` if this timeout was exceeded.
- *
- * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
- * cancellable suspending function inside the block throws [TimeoutException]. Normally that exception,
- * if uncaught by the block, gets converted into the `null` result of `withTimeoutOrNull`.
- * However, the code in the block can suppress [TimeoutException].
- *
- * The sibling function that throws exception on timeout is [withTimeout].
- * Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
- *
- * This function delegates to [Delay.invokeOnTimeout] if the context [CoroutineDispatcher]
- * implements [Delay] interface, otherwise it tracks time using a built-in single-threaded scheduled executor service.
- *
- * @param time timeout time
- * @param unit timeout unit (milliseconds by default)
- */
-public suspend fun <T> withTimeoutOrNull(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend () -> T): T? {
- require(time >= 0) { "Timeout time $time cannot be negative" }
- if (time <= 0L) return null
- return suspendCoroutineOrReturn { cont: Continuation<T?> ->
- val context = cont.context
- val completion = TimeoutOrNullCompletion(time, unit, cont)
- // schedule cancellation of this coroutine on time
- completion.disposeOnCompletion(context.delay.invokeOnTimeout(time, unit, completion))
- completion.initParentJob(context[Job])
- // restart block using new coroutine with new job,
- // however start it as undispatched coroutine, because we are already in the proper context
- try {
- block.startCoroutineUninterceptedOrReturn(completion)
- } catch (e: TimeoutException) {
- // replace inner timeout exception on our coroutine with null result
- if (e.coroutine == completion) null else throw e
- }
- }
-}
-
-private class TimeoutOrNullCompletion<T>(
- time: Long,
- unit: TimeUnit,
- cont: Continuation<T?>
-) : TimeoutCompletion<T?, T>(time, unit, cont) {
- override fun resumeWithException(exception: Throwable) {
- // suppress inner timeout exception and replace it with null
- if (exception is TimeoutException && exception.coroutine === this)
- cont.resumeDirect(null) else
- cont.resumeDirectWithException(exception)
- }
-}
-
-/**
- * This exception is thrown by [withTimeout] to indicate timeout.
- */
-public class TimeoutException internal constructor(
- message: String,
- @JvmField internal val coroutine: Job?
-) : CancellationException(message) {
- /**
- * Creates timeout exception with a given message.
- */
- public constructor(message: String) : this(message, null)
-}
-
-private fun TimeoutException(
- time: Long,
- unit: TimeUnit,
- coroutine: Job
-) : TimeoutException = TimeoutException("Timed out waiting for $time $unit", coroutine)
diff --git a/pom.xml b/pom.xml
index 1a35869..ff6a07b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,11 +23,11 @@
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<packaging>pom</packaging>
+ <name>kotlinx-coroutines</name>
<description>Coroutines support libraries for Kotlin 1.1</description>
-
<url>https://github.com/Kotlin/kotlinx.coroutines/</url>
<licenses>
@@ -41,13 +41,14 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <kotlin.version>1.1.4</kotlin.version>
+ <kotlin.version>1.1.51</kotlin.version>
<dokka.version>0.9.15</dokka.version>
<junit.version>4.12</junit.version>
- <atomicfu.version>0.6</atomicfu.version>
+ <atomicfu.version>0.8</atomicfu.version>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<core.docs.url>https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/</core.docs.url>
+ <core.docs.file>${project.basedir}/../../core/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</core.docs.file>
</properties>
<prerequisites>
@@ -116,10 +117,11 @@
</distributionManagement>
<modules>
- <module>kotlinx-coroutines-core</module>
<module>benchmarks</module>
<module>site</module>
<module>knit</module>
+ <module>core/kotlinx-coroutines-core</module>
+ <module>core/kotlinx-coroutines-io</module>
<module>reactive/kotlinx-coroutines-reactive</module>
<module>reactive/kotlinx-coroutines-reactor</module>
<module>reactive/kotlinx-coroutines-rx1</module>
@@ -233,7 +235,7 @@
<sourceLinks>
<link>
<dir>${project.basedir}/src/main/kotlin</dir>
- <url>http://github.com/kotlin/kotlinx.coroutines/tree/master/${project.artifactId}/src/main/kotlin</url>
+ <url>http://github.com/kotlin/kotlinx.coroutines/tree/master/${subdir}/${project.artifactId}/src/main/kotlin</url>
<urlSuffix>#L</urlSuffix>
</link>
</sourceLinks>
@@ -364,6 +366,7 @@
</build>
<profiles>
+ <!-- Profile to build against snapshot version of Kotlin compiler -->
<profile>
<id>kotlin-snapshot</id>
<activation>
@@ -387,5 +390,38 @@
</repository>
</repositories>
</profile>
+ <!-- Profile to attach javadocs with dokka -->
+ <profile>
+ <id>javadoc</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <!-- documentation -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.jetbrains.dokka</groupId>
+ <artifactId>dokka-maven-plugin</artifactId>
+ <executions>
+ <!-- build dokka htmls and package list (for refs) -->
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>dokka</goal>
+ </goals>
+ </execution>
+ <!-- attach javadoc with dokka -->
+ <execution>
+ <id>attach-javadocs</id>
+ <phase>package</phase>
+ <goals>
+ <goal>javadocJar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
-</project>
\ No newline at end of file
+</project>
diff --git a/reactive/coroutines-guide-reactive.md b/reactive/coroutines-guide-reactive.md
index 823be69..fd8e30a 100644
--- a/reactive/coroutines-guide-reactive.md
+++ b/reactive/coroutines-guide-reactive.md
@@ -482,6 +482,7 @@
subject.onNext("three")
subject.onNext("four")
yield() // yield the main thread to the launched coroutine <--- HERE
+ subject.onComplete() // now complete subject's sequence to cancel consumer, too
}
```
@@ -519,6 +520,7 @@
broadcast.offer("three")
broadcast.offer("four")
yield() // yield the main thread to the launched coroutine
+ broadcast.close() // now close broadcast channel to cancel consumer, too
}
```
@@ -756,7 +758,8 @@
to the [CoroutineScope.coroutineContext] that is provided by [publish] builder. This way, all the coroutines that are
being launched here are [children](../coroutines-guide.md#children-of-a-coroutine) of the `publish`
coroutine and will get cancelled when the `publish` coroutine is cancelled or is otherwise completed.
-This implementation completes as soon as the original publisher completes.
+Moreover, since parent coroutine waits until all children are complete, this implementation fully
+merges all the received streams.
For a test, let us start with `rangeWithInterval` function from the previous example and write a
producer that sends its results twice with some delay:
@@ -799,6 +802,7 @@
3
4
12
+13
```
<!--- TEST -->
diff --git a/reactive/kotlinx-coroutines-reactive/pom.xml b/reactive/kotlinx-coroutines-reactive/pom.xml
index c6b56f9..eea5ec2 100644
--- a/reactive/kotlinx-coroutines-reactive/pom.xml
+++ b/reactive/kotlinx-coroutines-reactive/pom.xml
@@ -22,13 +22,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-reactive</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>reactive</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -43,7 +47,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
<link>
<url>http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/</url>
diff --git a/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Publish.kt
index 11dc70e..054f441 100644
--- a/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Publish.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Publish.kt
@@ -17,24 +17,22 @@
package kotlinx.coroutines.experimental.reactive
import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.channels.ClosedSendChannelException
+import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.ProducerScope
import kotlinx.coroutines.experimental.channels.SendChannel
-import kotlinx.coroutines.experimental.handleCoroutineException
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.selects.SelectClause2
import kotlinx.coroutines.experimental.selects.SelectInstance
import kotlinx.coroutines.experimental.sync.Mutex
import org.reactivestreams.Publisher
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold reactive [Publisher] that runs a given [block] in a coroutine.
- * Every time the returned publisher is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned publisher is subscribed, it starts a new coroutine.
* Coroutine emits items with `send`. Unsubscribing cancels running coroutine.
*
* Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
@@ -45,11 +43,21 @@
* | `send` | `onNext`
* | Normal completion or `close` without cause | `onComplete`
* | Failure with exception or `close` with cause | `onError`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
public fun <T> publish(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
block: suspend ProducerScope<T>.() -> Unit
-): Publisher<T> = Publisher<T> { subscriber ->
+): Publisher<T> = Publisher { subscriber ->
val newContext = newCoroutineContext(context)
val coroutine = PublisherCoroutine(newContext, subscriber)
coroutine.initParentJob(context[Job])
@@ -61,10 +69,10 @@
private const val CLOSED = -1L // closed, but have not signalled onCompleted/onError yet
private const val SIGNALLED = -2L // already signalled subscriber onCompleted/onError
-private class PublisherCoroutine<T>(
+private class PublisherCoroutine<in T>(
parentContext: CoroutineContext,
private val subscriber: Subscriber<T>
-) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, Subscription {
+) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, Subscription, SelectClause2<T, SendChannel<T>> {
override val channel: SendChannel<T> get() = this
// Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked
@@ -76,16 +84,13 @@
override val isFull: Boolean = mutex.isLocked
override fun close(cause: Throwable?): Boolean = cancel(cause)
- private fun sendException() =
- (state as? CompletedExceptionally)?.cause ?: ClosedSendChannelException(CLOSED_MESSAGE)
-
override fun offer(element: T): Boolean {
if (!mutex.tryLock()) return false
doLockedNext(element)
return true
}
- public suspend override fun send(element: T): Unit {
+ public suspend override fun send(element: T) {
// fast-path -- try send without suspension
if (offer(element)) return
// slow-path does suspend
@@ -97,18 +102,24 @@
doLockedNext(element)
}
- override fun <R> registerSelectSend(select: SelectInstance<R>, element: T, block: suspend () -> R) =
- mutex.registerSelectLock(select, null) {
+ override val onSend: SelectClause2<T, SendChannel<T>>
+ get() = this
+
+ // registerSelectSend
+ @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, element: T, block: suspend (SendChannel<T>) -> R) {
+ mutex.onLock.registerSelectClause2(select, null) {
doLockedNext(element)
- block()
+ block(this)
}
+ }
// assert: mutex.isLocked()
private fun doLockedNext(elem: T) {
// check if already closed for send
if (!isActive) {
doLockedSignalCompleted()
- throw sendException()
+ throw getCancellationException()
}
// notify subscriber
try {
@@ -120,7 +131,7 @@
} finally {
doLockedSignalCompleted()
}
- throw sendException()
+ throw getCancellationException()
}
// now update nRequested
while (true) { // lock-free loop on nRequested
@@ -190,7 +201,7 @@
}
}
- override fun onCancellation() {
+ override fun onCancellation(exceptionally: CompletedExceptionally?) {
while (true) { // lock-free loop for nRequested
val cur = _nRequested.value
if (cur == SIGNALLED) return // some other thread holding lock already signalled cancellation/completion
diff --git a/reactive/kotlinx-coroutines-reactor/pom.xml b/reactive/kotlinx-coroutines-reactor/pom.xml
index cf5a396..e290614 100644
--- a/reactive/kotlinx-coroutines-reactor/pom.xml
+++ b/reactive/kotlinx-coroutines-reactor/pom.xml
@@ -23,13 +23,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-reactor</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>reactive</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -43,7 +47,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
<link>
<url>https://projectreactor.io/docs/core/release/api/</url>
diff --git a/reactive/kotlinx-coroutines-reactor/src/main/kotlin/kotlinx/coroutines/experimental/reactor/Mono.kt b/reactive/kotlinx-coroutines-reactor/src/main/kotlin/kotlinx/coroutines/experimental/reactor/Mono.kt
index 9f9a0f3..96df040 100644
--- a/reactive/kotlinx-coroutines-reactor/src/main/kotlin/kotlinx/coroutines/experimental/reactor/Mono.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/main/kotlin/kotlinx/coroutines/experimental/reactor/Mono.kt
@@ -15,19 +15,17 @@
*/
package kotlinx.coroutines.experimental.reactor
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.CoroutineScope
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.*
import reactor.core.Disposable
import reactor.core.publisher.Mono
import reactor.core.publisher.MonoSink
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold [mono][Mono] that will run a given [block] in a coroutine.
- * Every time the returned mono is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned mono is subscribed, it starts a new coroutine.
* Coroutine returns a single, possibly null value. Unsubscribing cancels running coroutine.
*
* | **Coroutine action** | **Signal to sink**
@@ -35,10 +33,20 @@
* | Returns a non-null value | `success(value)`
* | Returns a null | `success`
* | Failure with exception or unsubscribe | `error`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
fun <T> mono(
- context: CoroutineContext,
- block: suspend CoroutineScope.() -> T?
+ context: CoroutineContext = DefaultDispatcher,
+ block: suspend CoroutineScope.() -> T?
): Mono<T> = Mono.create { sink ->
val newContext = newCoroutineContext(context)
val coroutine = MonoCoroutine(newContext, sink)
diff --git a/reactive/kotlinx-coroutines-rx-example/pom.xml b/reactive/kotlinx-coroutines-rx-example/pom.xml
index eb7eb03..d4e6ce7 100644
--- a/reactive/kotlinx-coroutines-rx-example/pom.xml
+++ b/reactive/kotlinx-coroutines-rx-example/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
diff --git a/reactive/kotlinx-coroutines-rx1/pom.xml b/reactive/kotlinx-coroutines-rx1/pom.xml
index cd6f72c..d1a8927 100644
--- a/reactive/kotlinx-coroutines-rx1/pom.xml
+++ b/reactive/kotlinx-coroutines-rx1/pom.xml
@@ -22,13 +22,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-rx1</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>reactive</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -43,7 +47,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
<link>
<url>http://reactivex.io/RxJava/1.x/javadoc/</url>
diff --git a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxCompletable.kt b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxCompletable.kt
index cb4355c..c1701f8 100644
--- a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxCompletable.kt
+++ b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxCompletable.kt
@@ -16,28 +16,36 @@
package kotlinx.coroutines.experimental.rx1
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.CoroutineScope
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.*
import rx.Completable
import rx.CompletableSubscriber
import rx.Subscription
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold [Completable] that runs a given [block] in a coroutine.
- * Every time the returned completable is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned completable is subscribed, it starts a new coroutine.
* Unsubscribing cancels running coroutine.
*
* | **Coroutine action** | **Signal to subscriber**
* | ------------------------------------- | ------------------------
* | Completes successfully | `onCompleted`
* | Failure with exception or unsubscribe | `onError`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
public fun rxCompletable(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
block: suspend CoroutineScope.() -> Unit
): Completable = Completable.create { subscriber ->
val newContext = newCoroutineContext(context)
diff --git a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxObservable.kt b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxObservable.kt
index f7c4b5e..e6679d3 100644
--- a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxObservable.kt
+++ b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxObservable.kt
@@ -17,25 +17,23 @@
package kotlinx.coroutines.experimental.rx1
import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.channels.ClosedSendChannelException
+import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.ProducerScope
import kotlinx.coroutines.experimental.channels.SendChannel
-import kotlinx.coroutines.experimental.handleCoroutineException
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.selects.SelectClause2
import kotlinx.coroutines.experimental.selects.SelectInstance
import kotlinx.coroutines.experimental.sync.Mutex
import rx.Observable
import rx.Producer
import rx.Subscriber
import rx.Subscription
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold [Observable] that runs a given [block] in a coroutine.
- * Every time the returned observable is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned observable is subscribed, it starts a new coroutine.
* Coroutine emits items with `send`. Unsubscribing cancels running coroutine.
*
* Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
@@ -46,9 +44,19 @@
* | `send` | `onNext`
* | Normal completion or `close` without cause | `onCompleted`
* | Failure with exception or `close` with cause | `onError`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
public fun <T> rxObservable(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
block: suspend ProducerScope<T>.() -> Unit
): Observable<T> = Observable.create { subscriber ->
val newContext = newCoroutineContext(context)
@@ -59,14 +67,13 @@
block.startCoroutine(coroutine, coroutine)
}
-private const val CLOSED_MESSAGE = "This subscription had already closed (completed or failed)"
private const val CLOSED = -1L // closed, but have not signalled onCompleted/onError yet
private const val SIGNALLED = -2L // already signalled subscriber onCompleted/onError
-private class RxObservableCoroutine<T>(
+private class RxObservableCoroutine<in T>(
parentContext: CoroutineContext,
private val subscriber: Subscriber<T>
-) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, Producer, Subscription {
+) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, Producer, Subscription, SelectClause2<T, SendChannel<T>> {
override val channel: SendChannel<T> get() = this
// Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked
@@ -78,16 +85,13 @@
override val isFull: Boolean = mutex.isLocked
override fun close(cause: Throwable?): Boolean = cancel(cause)
- private fun sendException() =
- (state as? CompletedExceptionally)?.cause ?: ClosedSendChannelException(CLOSED_MESSAGE)
-
override fun offer(element: T): Boolean {
if (!mutex.tryLock()) return false
doLockedNext(element)
return true
}
- public suspend override fun send(element: T): Unit {
+ public suspend override fun send(element: T) {
// fast-path -- try send without suspension
if (offer(element)) return
// slow-path does suspend
@@ -99,18 +103,24 @@
doLockedNext(element)
}
- override fun <R> registerSelectSend(select: SelectInstance<R>, element: T, block: suspend () -> R) =
- mutex.registerSelectLock(select, null) {
+ override val onSend: SelectClause2<T, SendChannel<T>>
+ get() = this
+
+ // registerSelectSend
+ @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, element: T, block: suspend (SendChannel<T>) -> R) {
+ mutex.onLock.registerSelectClause2(select, null) {
doLockedNext(element)
- block()
+ block(this)
}
+ }
// assert: mutex.isLocked()
private fun doLockedNext(elem: T) {
// check if already closed for send
if (!isActive) {
doLockedSignalCompleted()
- throw sendException()
+ throw getCancellationException()
}
// notify subscriber
try {
@@ -122,7 +132,7 @@
} finally {
doLockedSignalCompleted()
}
- throw sendException()
+ throw getCancellationException()
}
// now update nRequested
while (true) { // lock-free loop on nRequested
@@ -192,7 +202,7 @@
}
}
- override fun onCancellation() {
+ override fun onCancellation(exceptionally: CompletedExceptionally?) {
while (true) { // lock-free loop for nRequested
val cur = _nRequested.value
if (cur == SIGNALLED) return // some other thread holding lock already signalled cancellation/completion
diff --git a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxSingle.kt b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxSingle.kt
index 9764bcd..d0478de 100644
--- a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxSingle.kt
+++ b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxSingle.kt
@@ -16,28 +16,36 @@
package kotlinx.coroutines.experimental.rx1
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.CoroutineScope
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.*
import rx.Single
import rx.SingleSubscriber
import rx.Subscription
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold [Single] that runs a given [block] in a coroutine.
- * Every time the returned single is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned single is subscribed, it starts a new coroutine.
* Coroutine returns a single value. Unsubscribing cancels running coroutine.
*
* | **Coroutine action** | **Signal to subscriber**
* | ------------------------------------- | ------------------------
* | Returns a value | `onSuccess`
* | Failure with exception or unsubscribe | `onError`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
public fun <T> rxSingle(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
block: suspend CoroutineScope.() -> T
): Single<T> = Single.create { subscriber ->
val newContext = newCoroutineContext(context)
diff --git a/reactive/kotlinx-coroutines-rx2/pom.xml b/reactive/kotlinx-coroutines-rx2/pom.xml
index 05ccd85..bd1eff6 100644
--- a/reactive/kotlinx-coroutines-rx2/pom.xml
+++ b/reactive/kotlinx-coroutines-rx2/pom.xml
@@ -22,13 +22,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-rx2</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>reactive</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -43,7 +47,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
<link>
<url>http://reactivex.io/RxJava/javadoc/</url>
diff --git a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxCompletable.kt b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxCompletable.kt
index d2cdffd..52bfc94 100644
--- a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxCompletable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxCompletable.kt
@@ -19,25 +19,33 @@
import io.reactivex.Completable
import io.reactivex.CompletableEmitter
import io.reactivex.functions.Cancellable
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.CoroutineScope
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.*
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold [Completable] that runs a given [block] in a coroutine.
- * Every time the returned completable is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned completable is subscribed, it starts a new coroutine.
* Unsubscribing cancels running coroutine.
*
* | **Coroutine action** | **Signal to subscriber**
* | ------------------------------------- | ------------------------
* | Completes successfully | `onCompleted`
* | Failure with exception or unsubscribe | `onError`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
public fun rxCompletable(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
block: suspend CoroutineScope.() -> Unit
): Completable = Completable.create { subscriber ->
val newContext = newCoroutineContext(context)
diff --git a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxMaybe.kt b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxMaybe.kt
index c1ed043..a0fd3c6 100644
--- a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxMaybe.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxMaybe.kt
@@ -19,16 +19,14 @@
import io.reactivex.Maybe
import io.reactivex.MaybeEmitter
import io.reactivex.functions.Cancellable
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.CoroutineScope
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.*
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold [maybe][Maybe] that will run a given [block] in a coroutine.
- * Every time the returned observable is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned observable is subscribed, it starts a new coroutine.
* Coroutine returns a single, possibly null value. Unsubscribing cancels running coroutine.
*
* | **Coroutine action** | **Signal to subscriber**
@@ -36,9 +34,19 @@
* | Returns a non-null value | `onSuccess`
* | Returns a null | `onComplete`
* | Failure with exception or unsubscribe | `onError`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
public fun <T> rxMaybe(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
block: suspend CoroutineScope.() -> T?
): Maybe<T> = Maybe.create { subscriber ->
val newContext = newCoroutineContext(context)
diff --git a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxObservable.kt b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxObservable.kt
index 5409e44..65e3ddf 100644
--- a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxObservable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxObservable.kt
@@ -20,21 +20,19 @@
import io.reactivex.ObservableEmitter
import io.reactivex.functions.Cancellable
import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.channels.ClosedSendChannelException
+import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.ProducerScope
import kotlinx.coroutines.experimental.channels.SendChannel
-import kotlinx.coroutines.experimental.handleCoroutineException
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.selects.SelectClause2
import kotlinx.coroutines.experimental.selects.SelectInstance
import kotlinx.coroutines.experimental.sync.Mutex
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold [observable][Observable] that will run a given [block] in a coroutine.
- * Every time the returned observable is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned observable is subscribed, it starts a new coroutine.
* Coroutine emits items with `send`. Unsubscribing cancels running coroutine.
*
* Invocations of `send` are suspended appropriately to ensure that `onNext` is not invoked concurrently.
@@ -45,9 +43,19 @@
* | `send` | `onNext`
* | Normal completion or `close` without cause | `onComplete`
* | Failure with exception or `close` with cause | `onError`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
public fun <T> rxObservable(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
block: suspend ProducerScope<T>.() -> Unit
): Observable<T> = Observable.create { subscriber ->
val newContext = newCoroutineContext(context)
@@ -57,7 +65,6 @@
block.startCoroutine(coroutine, coroutine)
}
-private const val CLOSED_MESSAGE = "This subscription had already closed (completed or failed)"
private const val OPEN = 0 // open channel, still working
private const val CLOSED = -1 // closed, but have not signalled onCompleted/onError yet
private const val SIGNALLED = -2 // already signalled subscriber onCompleted/onError
@@ -65,7 +72,7 @@
private class RxObservableCoroutine<T>(
parentContext: CoroutineContext,
private val subscriber: ObservableEmitter<T>
-) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, Cancellable {
+) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, Cancellable, SelectClause2<T, SendChannel<T>> {
override val channel: SendChannel<T> get() = this
// Mutex is locked when while subscriber.onXXX is being invoked
@@ -77,16 +84,13 @@
override val isFull: Boolean = mutex.isLocked
override fun close(cause: Throwable?): Boolean = cancel(cause)
- private fun sendException() =
- (state as? CompletedExceptionally)?.cause ?: ClosedSendChannelException(CLOSED_MESSAGE)
-
override fun offer(element: T): Boolean {
if (!mutex.tryLock()) return false
doLockedNext(element)
return true
}
- public suspend override fun send(element: T): Unit {
+ public suspend override fun send(element: T) {
// fast-path -- try send without suspension
if (offer(element)) return
// slow-path does suspend
@@ -98,18 +102,24 @@
doLockedNext(element)
}
- override fun <R> registerSelectSend(select: SelectInstance<R>, element: T, block: suspend () -> R) =
- mutex.registerSelectLock(select, null) {
+ override val onSend: SelectClause2<T, SendChannel<T>>
+ get() = this
+
+ // registerSelectSend
+ @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
+ override fun <R> registerSelectClause2(select: SelectInstance<R>, element: T, block: suspend (SendChannel<T>) -> R) {
+ mutex.onLock.registerSelectClause2(select, null) {
doLockedNext(element)
- block()
+ block(this)
}
+ }
// assert: mutex.isLocked()
private fun doLockedNext(elem: T) {
// check if already closed for send
if (!isActive) {
doLockedSignalCompleted()
- throw sendException()
+ throw getCancellationException()
}
// notify subscriber
try {
@@ -121,7 +131,7 @@
} finally {
doLockedSignalCompleted()
}
- throw sendException()
+ throw getCancellationException()
}
/*
There is no sense to check for `isActive` before doing `unlock`, because cancellation/completion might
@@ -155,7 +165,7 @@
}
}
- override fun onCancellation() {
+ override fun onCancellation(exceptionally: CompletedExceptionally?) {
if (!_signal.compareAndSet(OPEN, CLOSED)) return // abort, other thread invoked doLockedSignalCompleted
if (mutex.tryLock()) // if we can acquire the lock
doLockedSignalCompleted()
diff --git a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxSingle.kt b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxSingle.kt
index 6b7d1f2..ef9b154 100644
--- a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxSingle.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxSingle.kt
@@ -19,25 +19,33 @@
import io.reactivex.Single
import io.reactivex.SingleEmitter
import io.reactivex.functions.Cancellable
-import kotlinx.coroutines.experimental.AbstractCoroutine
-import kotlinx.coroutines.experimental.CoroutineScope
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.*
+import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.startCoroutine
/**
* Creates cold [single][Single] that will run a given [block] in a coroutine.
- * Every time the returned observable is subscribed, it starts a new coroutine in the specified [context].
+ * Every time the returned observable is subscribed, it starts a new coroutine.
* Coroutine returns a single value. Unsubscribing cancels running coroutine.
*
* | **Coroutine action** | **Signal to subscriber**
* | ------------------------------------- | ------------------------
* | Returns a value | `onSuccess`
* | Failure with exception or unsubscribe | `onError`
+ *
+ * The [context] for the new coroutine can be explicitly specified.
+ * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
+ * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
+ * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param block the coroutine code.
*/
+@JvmOverloads // for binary compatibility with older code compiled before context had a default
public fun <T> rxSingle(
- context: CoroutineContext,
+ context: CoroutineContext = DefaultDispatcher,
block: suspend CoroutineScope.() -> T
): Single<T> = Single.create { subscriber ->
val newContext = newCoroutineContext(context)
diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-08.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-08.kt
index f9e1a85..dc78fff 100644
--- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-08.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-08.kt
@@ -34,4 +34,5 @@
subject.onNext("three")
subject.onNext("four")
yield() // yield the main thread to the launched coroutine <--- HERE
+ subject.onComplete() // now complete subject's sequence to cancel consumer, too
}
diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-09.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-09.kt
index 18d7768..34f88e3 100644
--- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-09.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-09.kt
@@ -34,4 +34,5 @@
broadcast.offer("three")
broadcast.offer("four")
yield() // yield the main thread to the launched coroutine
+ broadcast.close() // now close broadcast channel to cancel consumer, too
}
diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/test/GuideReactiveTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/test/GuideReactiveTest.kt
index 7d84485..4597334 100644
--- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/test/GuideReactiveTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/test/GuideReactiveTest.kt
@@ -137,7 +137,8 @@
"11",
"3",
"4",
- "12"
+ "12",
+ "13"
)
}
diff --git a/site/build.xml b/site/build.xml
index e67458c..b6e616e 100644
--- a/site/build.xml
+++ b/site/build.xml
@@ -33,7 +33,8 @@
<target name="site" depends="knit">
<copy todir="target">
<fileset dir="docs"/>
- <fileset dir="../kotlinx-coroutines-core/target/dokka" includes="**/*.md"/>
+ <fileset dir="../core/kotlinx-coroutines-core/target/dokka" includes="**/*.md"/>
+ <fileset dir="../core/kotlinx-coroutines-io/target/dokka" includes="**/*.md"/>
<fileset dir="../reactive/kotlinx-coroutines-reactive/target/dokka" includes="**/*.md"/>
<fileset dir="../reactive/kotlinx-coroutines-reactor/target/dokka" includes="**/*.md"/>
<fileset dir="../reactive/kotlinx-coroutines-rx1/target/dokka" includes="**/*.md"/>
diff --git a/site/docs/index.md b/site/docs/index.md
index 39057bd..3aff530 100644
--- a/site/docs/index.md
+++ b/site/docs/index.md
@@ -11,6 +11,7 @@
## Modules
[kotlinx-coroutines-core](kotlinx-coroutines-core) | Core primitives to work with coroutines
+[kotlinx-coroutines-io](kotlinx-coroutines-io) | Byte I/O channels (_unstable_, work in progress)
[kotlinx-coroutines-reactive](kotlinx-coroutines-reactive) | Utilities for [Reactive Streams](http://www.reactive-streams.org)
[kotlinx-coroutines-reactor](kotlinx-coroutines-reactor) | Utilities for [Reactor](https://projectreactor.io)
[kotlinx-coroutines-rx1](kotlinx-coroutines-rx1) | Utilities for [RxJava 1.x](https://github.com/ReactiveX/RxJava/tree/1.x)
diff --git a/site/pom.xml b/site/pom.xml
index 3ebcd0c..a4a916b 100644
--- a/site/pom.xml
+++ b/site/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
</parent>
<artifactId>kotlinx-coroutines-site</artifactId>
@@ -36,6 +36,11 @@
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-coroutines-io</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactive</artifactId>
<version>${project.version}</version>
</dependency>
diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md
index 91f3262..7f0dfa6 100644
--- a/ui/coroutines-guide-ui.md
+++ b/ui/coroutines-guide-ui.md
@@ -173,7 +173,7 @@
`app/build.gradle` file:
```groovy
-compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.18"
+compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19"
```
Coroutines are experimental feature in Kotlin.
diff --git a/ui/kotlinx-coroutines-android/example-app/app/build.gradle b/ui/kotlinx-coroutines-android/example-app/app/build.gradle
index b6bad79..12068dc 100644
--- a/ui/kotlinx-coroutines-android/example-app/app/build.gradle
+++ b/ui/kotlinx-coroutines-android/example-app/app/build.gradle
@@ -36,7 +36,7 @@
compile 'com.android.support:design:25.2.0'
testCompile 'junit:junit:4.12'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
- compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.18"
+ compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19"
}
kotlin {
diff --git a/ui/kotlinx-coroutines-android/example-app/build.gradle b/ui/kotlinx-coroutines-android/example-app/build.gradle
index 0f912b2..51672f9 100644
--- a/ui/kotlinx-coroutines-android/example-app/build.gradle
+++ b/ui/kotlinx-coroutines-android/example-app/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.1.4'
+ ext.kotlin_version = '1.1.51'
repositories {
jcenter()
}
diff --git a/ui/kotlinx-coroutines-android/pom.xml b/ui/kotlinx-coroutines-android/pom.xml
index e79e37a..f34fb45 100644
--- a/ui/kotlinx-coroutines-android/pom.xml
+++ b/ui/kotlinx-coroutines-android/pom.xml
@@ -22,13 +22,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-android</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>ui</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -42,7 +46,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
<link>
<url>https://developer.android.com/reference/</url>
diff --git a/ui/kotlinx-coroutines-javafx/pom.xml b/ui/kotlinx-coroutines-javafx/pom.xml
index 9210e10..15fc9a2 100644
--- a/ui/kotlinx-coroutines-javafx/pom.xml
+++ b/ui/kotlinx-coroutines-javafx/pom.xml
@@ -22,13 +22,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-javafx</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>ui</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -42,7 +46,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
</externalDocumentationLinks>
<skip>false</skip>
@@ -58,6 +62,13 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-coroutines-core</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/ui/kotlinx-coroutines-javafx/src/main/kotlin/kotlinx/coroutines/experimental/javafx/JavaFx.kt b/ui/kotlinx-coroutines-javafx/src/main/kotlin/kotlinx/coroutines/experimental/javafx/JavaFx.kt
index b1587c5..21b35c8 100644
--- a/ui/kotlinx-coroutines-javafx/src/main/kotlin/kotlinx/coroutines/experimental/javafx/JavaFx.kt
+++ b/ui/kotlinx-coroutines-javafx/src/main/kotlin/kotlinx/coroutines/experimental/javafx/JavaFx.kt
@@ -16,6 +16,7 @@
package kotlinx.coroutines.experimental.javafx
+import com.sun.javafx.application.PlatformImpl
import javafx.animation.AnimationTimer
import javafx.animation.KeyFrame
import javafx.animation.Timeline
@@ -29,11 +30,15 @@
import java.util.concurrent.TimeUnit
import kotlin.coroutines.experimental.CoroutineContext
-
/**
* Dispatches execution onto JavaFx application thread and provides native [delay] support.
*/
object JavaFx : CoroutineDispatcher(), Delay {
+ init {
+ // :kludge: to make sure Toolkit is initialized if we use JavaFx dispatcher outside of JavaFx app
+ initPlatform()
+ }
+
private val pulseTimer by lazy {
PulseTimer().apply { start() }
}
@@ -87,3 +92,7 @@
override fun toString() = "JavaFx"
}
+
+internal fun initPlatform() {
+ PlatformImpl.startup {}
+}
\ No newline at end of file
diff --git a/ui/kotlinx-coroutines-javafx/src/test/kotlin/kotlinx/coroutines/experimental/javafx/JavaFxTest.kt b/ui/kotlinx-coroutines-javafx/src/test/kotlin/kotlinx/coroutines/experimental/javafx/JavaFxTest.kt
new file mode 100644
index 0000000..ee4f5fc
--- /dev/null
+++ b/ui/kotlinx-coroutines-javafx/src/test/kotlin/kotlinx/coroutines/experimental/javafx/JavaFxTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental.javafx
+
+import guide.test.ignoreLostThreads
+import javafx.application.Platform
+import kotlinx.coroutines.experimental.TestBase
+import kotlinx.coroutines.experimental.delay
+import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.experimental.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+class JavaFxTest : TestBase() {
+ @Before
+ fun setup() {
+ ignoreLostThreads("JavaFX Application Thread", "Thread-", "QuantumRenderer-")
+ }
+
+ @Test
+ fun testDelay() {
+ try {
+ initPlatform()
+ } catch (e: UnsupportedOperationException) {
+ println("Skipping JavaFxTest in headless environment")
+ return // ignore test in headless environments
+ }
+
+ runBlocking {
+ expect(1)
+ val job = launch(JavaFx) {
+ check(Platform.isFxApplicationThread())
+ expect(2)
+ delay(100)
+ check(Platform.isFxApplicationThread())
+ expect(3)
+ }
+ job.join()
+ finish(4)
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/kotlinx-coroutines-swing/pom.xml b/ui/kotlinx-coroutines-swing/pom.xml
index 82edd53..67b2277 100644
--- a/ui/kotlinx-coroutines-swing/pom.xml
+++ b/ui/kotlinx-coroutines-swing/pom.xml
@@ -22,13 +22,17 @@
<parent>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines</artifactId>
- <version>0.18-SNAPSHOT</version>
+ <version>0.19-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>kotlinx-coroutines-swing</artifactId>
<packaging>jar</packaging>
+ <properties>
+ <subdir>ui</subdir>
+ </properties>
+
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
@@ -42,7 +46,7 @@
<externalDocumentationLinks combine.children="append">
<link>
<url>${core.docs.url}</url>
- <packageListUrl>file:///${project.parent.basedir}/kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core/package-list</packageListUrl>
+ <packageListUrl>file:///${core.docs.file}</packageListUrl>
</link>
</externalDocumentationLinks>
<skip>false</skip>