From 55a66acc03a918370a8962cd2f41a22c1c1e2020 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Mon, 12 Mar 2018 20:15:07 +0300 Subject: [PATCH 01/61] Improve ReceiveChannel operators implementations to guarantee closing of the source channels under all circumstances; `onCompletion` added to `produce` builder; `ReceiveChannel.consumes(): CompletionHandler` extension fun. Fixes #279 --- .../experimental/channels/Channels.kt | 490 +++++----- .../experimental/channels/Produce.kt | 12 + .../channels/ChannelsConsumeTest.kt | 911 ++++++++++++++++++ 3 files changed, 1188 insertions(+), 225 deletions(-) create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsConsumeTest.kt diff --git a/core/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 index 4623eceefc..87abbf567c 100644 --- a/core/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 @@ -78,7 +78,7 @@ public inline fun BroadcastChannel.consume(block: SubscriptionReceiveC /** * Subscribes to this [BroadcastChannel] and performs the specified action for each received element. */ -public inline suspend fun BroadcastChannel.consumeEach(action: (E) -> Unit) = +public suspend inline fun BroadcastChannel.consumeEach(action: (E) -> Unit) = consume { for (element in this) action(element) } @@ -92,25 +92,79 @@ public suspend fun BroadcastChannel.consumeEach(action: suspend (E) -> Un // -------- Operations on ReceiveChannel -------- +/** + * Returns a [CompletionHandler] that invokes [cancel][ReceiveChannel.cancel] on the [ReceiveChannel] + * with the corresponding cause. See also [ReceiveChannel.consume]. + * + * **WARNING**: It is planned that in the future a second invocation of this method + * on an channel that is already being consumed is going to fail fast, that is + * immediately throw an [IllegalStateException]. + * See [this issue](https://github.com/Kotlin/kotlinx.coroutines/issues/167) + * for details. + */ +public fun ReceiveChannel<*>.consumes(): CompletionHandler = + { cause: Throwable? -> cancel(cause) } + +/** + * Returns a [CompletionHandler] that invokes [cancel][ReceiveChannel.cancel] on all the + * specified [ReceiveChannel] instances with the corresponding cause. + * See also [ReceiveChannel.consumes()] for a version on one channel. + */ +public fun consumesAll(vararg channels: ReceiveChannel<*>): CompletionHandler = + { cause: Throwable? -> + var exception: Throwable? = null + for (channel in channels) + try { + channel.cancel(cause) + } catch (e: Throwable) { + if (exception == null) { + exception = e + } else { + exception.addSuppressed(e) + } + } + exception?.let { throw it } + } + /** * Makes sure that the given [block] consumes all elements from the given channel * by always invoking [cancel][ReceiveChannel.cancel] after the execution of the block. + * + * **WARNING**: It is planned that in the future a second invocation of this method + * on an channel that is already being consumed is going to fail fast, that is + * immediately throw an [IllegalStateException]. + * See [this issue](https://github.com/Kotlin/kotlinx.coroutines/issues/167) + * for details. + * + * The operation is _terminal_. */ -public inline fun ReceiveChannel.consume(block: ReceiveChannel.() -> R): R = +public inline fun ReceiveChannel.consume(block: ReceiveChannel.() -> R): R { + var cause: Throwable? = null try { - block() + return block() + } catch (e: Throwable) { + cause = e + throw e } finally { - cancel() + cancel(cause) } +} /** * Performs the given [action] for each received element. * - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * **WARNING**: It is planned that in the future a second invocation of this method + * on an channel that is already being consumed is going to fail fast, that is + * immediately throw an [IllegalStateException]. + * See [this issue](https://github.com/Kotlin/kotlinx.coroutines/issues/167) + * for details. + * + * The operation is _terminal_. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.consumeEach(action: (E) -> Unit) = +public suspend inline fun ReceiveChannel.consumeEach(action: (E) -> Unit) = consume { - for (element in this) action(element) + for (e in this) action(e) } /** @@ -123,9 +177,10 @@ public suspend fun ReceiveChannel.consumeEach(action: suspend (E) -> Unit /** * Performs the given [action] for each received element. * - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * The operation is _terminal_. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.consumeEachIndexed(action: (IndexedValue) -> Unit) { +public suspend inline fun ReceiveChannel.consumeEachIndexed(action: (IndexedValue) -> Unit) { var index = 0 consumeEach { action(IndexedValue(index++, it)) @@ -136,7 +191,7 @@ public inline suspend fun ReceiveChannel.consumeEachIndexed(action: (Inde * Returns an element at the given [index] or throws an [IndexOutOfBoundsException] if the [index] is out of bounds of this channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.elementAt(index: Int): E = elementAtOrElse(index) { throw IndexOutOfBoundsException("ReceiveChannel doesn't contain element at index $index.") } @@ -145,9 +200,9 @@ public suspend fun ReceiveChannel.elementAt(index: Int): E = * Returns an element at the given [index] or the result of calling the [defaultValue] function if the [index] is out of bounds of this channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.elementAtOrElse(index: Int, defaultValue: (Int) -> E): E = +public suspend inline fun ReceiveChannel.elementAtOrElse(index: Int, defaultValue: (Int) -> E): E = consume { if (index < 0) return defaultValue(index) @@ -163,7 +218,7 @@ public inline suspend fun ReceiveChannel.elementAtOrElse(index: Int, defa * Returns an element at the given [index] or `null` if the [index] is out of bounds of this channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.elementAtOrNull(index: Int): E? = consume { @@ -181,18 +236,18 @@ public suspend fun ReceiveChannel.elementAtOrNull(index: Int): E? = * Returns the first element matching the given [predicate], or `null` if no such element was found. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.find(predicate: (E) -> Boolean): E? = +public suspend inline fun ReceiveChannel.find(predicate: (E) -> Boolean): E? = firstOrNull(predicate) /** * Returns the last element matching the given [predicate], or `null` if no such element was found. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.findLast(predicate: (E) -> Boolean): E? = +public suspend inline fun ReceiveChannel.findLast(predicate: (E) -> Boolean): E? = lastOrNull(predicate) /** @@ -200,7 +255,7 @@ public inline suspend fun ReceiveChannel.findLast(predicate: (E) -> Boole * @throws [NoSuchElementException] if the channel is empty. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.first(): E = consume { @@ -215,9 +270,9 @@ public suspend fun ReceiveChannel.first(): E = * @throws [NoSuchElementException] if no such element is found. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.first(predicate: (E) -> Boolean): E { +public suspend inline fun ReceiveChannel.first(predicate: (E) -> Boolean): E { consumeEach { if (predicate(it)) return it } @@ -228,7 +283,7 @@ public inline suspend fun ReceiveChannel.first(predicate: (E) -> Boolean) * Returns the first element, or `null` if the channel is empty. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.firstOrNull(): E? = consume { @@ -242,7 +297,7 @@ public suspend fun ReceiveChannel.firstOrNull(): E? = * Returns the first element matching the given [predicate], or `null` if element was not found. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public inline suspend fun ReceiveChannel.firstOrNull(predicate: (E) -> Boolean): E? { consumeEach { @@ -255,7 +310,7 @@ public inline suspend fun ReceiveChannel.firstOrNull(predicate: (E) -> Bo * Returns first index of [element], or -1 if the channel does not contain element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.indexOf(element: E): Int { var index = 0 @@ -271,9 +326,9 @@ public suspend fun ReceiveChannel.indexOf(element: E): Int { * Returns index of the first element matching the given [predicate], or -1 if the channel does not contain such element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.indexOfFirst(predicate: (E) -> Boolean): Int { +public suspend inline fun ReceiveChannel.indexOfFirst(predicate: (E) -> Boolean): Int { var index = 0 consumeEach { if (predicate(it)) @@ -287,7 +342,7 @@ public inline suspend fun ReceiveChannel.indexOfFirst(predicate: (E) -> B * Returns index of the last element matching the given [predicate], or -1 if the channel does not contain such element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public inline suspend fun ReceiveChannel.indexOfLast(predicate: (E) -> Boolean): Int { var lastIndex = -1 @@ -305,7 +360,7 @@ public inline suspend fun ReceiveChannel.indexOfLast(predicate: (E) -> Bo * @throws [NoSuchElementException] if the channel is empty. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.last(): E = consume { @@ -323,9 +378,9 @@ public suspend fun ReceiveChannel.last(): E = * @throws [NoSuchElementException] if no such element is found. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.last(predicate: (E) -> Boolean): E { +public suspend inline fun ReceiveChannel.last(predicate: (E) -> Boolean): E { var last: E? = null var found = false consumeEach { @@ -343,7 +398,7 @@ public inline suspend fun ReceiveChannel.last(predicate: (E) -> Boolean): * Returns last index of [element], or -1 if the channel does not contain element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.lastIndexOf(element: E): Int { var lastIndex = -1 @@ -360,7 +415,7 @@ public suspend fun ReceiveChannel.lastIndexOf(element: E): Int { * Returns the last element, or `null` if the channel is empty. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.lastOrNull(): E? = consume { @@ -377,9 +432,9 @@ public suspend fun ReceiveChannel.lastOrNull(): E? = * Returns the last element matching the given [predicate], or `null` if no such element was found. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.lastOrNull(predicate: (E) -> Boolean): E? { +public suspend inline fun ReceiveChannel.lastOrNull(predicate: (E) -> Boolean): E? { var last: E? = null consumeEach { if (predicate(it)) { @@ -393,7 +448,7 @@ public inline suspend fun ReceiveChannel.lastOrNull(predicate: (E) -> Boo * Returns the single element, or throws an exception if the channel is empty or has more than one element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.single(): E = consume { @@ -410,9 +465,9 @@ public suspend fun ReceiveChannel.single(): E = * Returns the single element matching the given [predicate], or throws exception if there is no or more than one matching element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.single(predicate: (E) -> Boolean): E { +public suspend inline fun ReceiveChannel.single(predicate: (E) -> Boolean): E { var single: E? = null var found = false consumeEach { @@ -431,7 +486,7 @@ public inline suspend fun ReceiveChannel.single(predicate: (E) -> Boolean * Returns single element, or `null` if the channel is empty or has more than one element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.singleOrNull(): E? = consume { @@ -448,9 +503,9 @@ public suspend fun ReceiveChannel.singleOrNull(): E? = * Returns the single element matching the given [predicate], or `null` if element was not found or more than one element was found. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.singleOrNull(predicate: (E) -> Boolean): E? { +public suspend inline fun ReceiveChannel.singleOrNull(predicate: (E) -> Boolean): E? { var single: E? = null var found = false consumeEach { @@ -468,22 +523,20 @@ public inline suspend fun ReceiveChannel.singleOrNull(predicate: (E) -> B * Returns a channel containing all elements except first [n] elements. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public fun ReceiveChannel.drop(n: Int, context: CoroutineContext = Unconfined): ReceiveChannel = - produce(context) { - consume { - require(n >= 0) { "Requested element count $n is less than zero." } - var remaining: Int = n - if (remaining > 0) - for (element in this@drop) { - remaining-- - if (remaining == 0) - break - } - for (element in this@drop) { - send(element) + produce(context, onCompletion = consumes()) { + require(n >= 0) { "Requested element count $n is less than zero." } + var remaining: Int = n + if (remaining > 0) + for (e in this@drop) { + remaining-- + if (remaining == 0) + break } + for (e in this@drop) { + send(e) } } @@ -491,35 +544,33 @@ public fun ReceiveChannel.drop(n: Int, context: CoroutineContext = Unconf * Returns a channel containing all elements except first elements that satisfy the given [predicate]. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.dropWhile(context: CoroutineContext = Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel = - produce(context) { - consume { - for (element in this@dropWhile) { - if (!predicate(element)) { - send(element) - break - } - } - for (element in this@dropWhile) { - send(element) + produce(context, onCompletion = consumes()) { + for (e in this@dropWhile) { + if (!predicate(e)) { + send(e) + break } } + for (e in this@dropWhile) { + send(e) + } } /** * Returns a channel containing only elements matching the given [predicate]. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.filter(context: CoroutineContext = Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel = - produce(context) { - consumeEach { - if (predicate(it)) send(it) + produce(context, onCompletion = consumes()) { + for (e in this@filter) { + if (predicate(e)) send(e) } } @@ -529,14 +580,14 @@ public fun ReceiveChannel.filter(context: CoroutineContext = Unconfined, * and returns the result of predicate evaluation on the element. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.filterIndexed(context: CoroutineContext = Unconfined, predicate: suspend (index: Int, E) -> Boolean): ReceiveChannel = - produce(context) { + produce(context, onCompletion = consumes()) { var index = 0 - consumeEach { - if (predicate(index++, it)) send(it) + for (e in this@filterIndexed) { + if (predicate(index++, e)) send(e) } } @@ -546,9 +597,9 @@ public fun ReceiveChannel.filterIndexed(context: CoroutineContext = Uncon * and returns the result of predicate evaluation on the element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.filterIndexedTo(destination: C, predicate: (index: Int, E) -> Boolean): C { +public suspend inline fun > ReceiveChannel.filterIndexedTo(destination: C, predicate: (index: Int, E) -> Boolean): C { consumeEachIndexed { (index, element) -> if (predicate(index, element)) destination.add(element) } @@ -561,9 +612,9 @@ public inline suspend fun > ReceiveChannel.fil * and returns the result of predicate evaluation on the element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.filterIndexedTo(destination: C, predicate: (index: Int, E) -> Boolean): C { +public suspend inline fun > ReceiveChannel.filterIndexedTo(destination: C, predicate: (index: Int, E) -> Boolean): C { consumeEachIndexed { (index, element) -> if (predicate(index, element)) destination.send(element) } @@ -574,18 +625,13 @@ public inline suspend fun > ReceiveChannel.filterIndexe * Returns a channel containing all elements not matching the given [predicate]. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.filterNot(context: CoroutineContext = Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel = filter(context) { !predicate(it) } /** - * Returns a channel containing all elements not matching the given [predicate]. - * - * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. - * * @suppress **Deprecated**: For binary compatibility only */ @Deprecated("For binary compatibility only", level = DeprecationLevel.HIDDEN) @@ -595,7 +641,7 @@ public fun ReceiveChannel.filterNot(predicate: suspend (E) -> Boolean): R * Returns a channel containing all elements that are not `null`. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ @Suppress("UNCHECKED_CAST") public fun ReceiveChannel.filterNotNull(): ReceiveChannel = @@ -605,7 +651,7 @@ public fun ReceiveChannel.filterNotNull(): ReceiveChannel = * Appends all elements that are not `null` to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun > ReceiveChannel.filterNotNullTo(destination: C): C { consumeEach { @@ -618,7 +664,7 @@ public suspend fun > ReceiveChannel.fil * Appends all elements that are not `null` to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun > ReceiveChannel.filterNotNullTo(destination: C): C { consumeEach { @@ -631,9 +677,9 @@ public suspend fun > ReceiveChannel.filterNotNul * Appends all elements not matching the given [predicate] to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.filterNotTo(destination: C, predicate: (E) -> Boolean): C { +public suspend inline fun > ReceiveChannel.filterNotTo(destination: C, predicate: (E) -> Boolean): C { consumeEach { if (!predicate(it)) destination.add(it) } @@ -644,9 +690,9 @@ public inline suspend fun > ReceiveChannel.fil * Appends all elements not matching the given [predicate] to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.filterNotTo(destination: C, predicate: (E) -> Boolean): C { +public suspend inline fun > ReceiveChannel.filterNotTo(destination: C, predicate: (E) -> Boolean): C { consumeEach { if (!predicate(it)) destination.send(it) } @@ -657,9 +703,9 @@ public inline suspend fun > ReceiveChannel.filterNotTo( * Appends all elements matching the given [predicate] to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.filterTo(destination: C, predicate: (E) -> Boolean): C { +public suspend inline fun > ReceiveChannel.filterTo(destination: C, predicate: (E) -> Boolean): C { consumeEach { if (predicate(it)) destination.add(it) } @@ -670,9 +716,9 @@ public inline suspend fun > ReceiveChannel.fil * Appends all elements matching the given [predicate] to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.filterTo(destination: C, predicate: (E) -> Boolean): C { +public suspend inline fun > ReceiveChannel.filterTo(destination: C, predicate: (E) -> Boolean): C { consumeEach { if (predicate(it)) destination.send(it) } @@ -683,20 +729,18 @@ public inline suspend fun > ReceiveChannel.filterTo(des * Returns a channel containing first [n] elements. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public fun ReceiveChannel.take(n: Int, context: CoroutineContext = Unconfined): ReceiveChannel = - produce(context) { - consume { - if (n == 0) return@produce - require(n >= 0) { "Requested element count $n is less than zero." } - var remaining: Int = n - for (element in this@take) { - send(element) - remaining-- - if (remaining == 0) - return@produce - } + produce(context, onCompletion = consumes()) { + if (n == 0) return@produce + require(n >= 0) { "Requested element count $n is less than zero." } + var remaining: Int = n + for (e in this@take) { + send(e) + remaining-- + if (remaining == 0) + return@produce } } @@ -704,14 +748,14 @@ public fun ReceiveChannel.take(n: Int, context: CoroutineContext = Unconf * Returns a channel containing first elements satisfying the given [predicate]. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.takeWhile(context: CoroutineContext = Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel = - produce(context) { - consumeEach { - if (!predicate(it)) return@produce - send(it) + produce(context, onCompletion = consumes()) { + for (e in this@takeWhile) { + if (!predicate(e)) return@produce + send(e) } } @@ -724,9 +768,9 @@ public fun ReceiveChannel.takeWhile(context: CoroutineContext = Unconfine * The returned map preserves the entry iteration order of the original channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.associate(transform: (E) -> Pair): Map = +public suspend inline fun ReceiveChannel.associate(transform: (E) -> Pair): Map = associateTo(LinkedHashMap(), transform) /** @@ -738,9 +782,9 @@ public inline suspend fun ReceiveChannel.associate(transform: (E) - * The returned map preserves the entry iteration order of the original channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.associateBy(keySelector: (E) -> K): Map = +public suspend inline fun ReceiveChannel.associateBy(keySelector: (E) -> K): Map = associateByTo(LinkedHashMap(), keySelector) /** @@ -751,9 +795,9 @@ public inline suspend fun ReceiveChannel.associateBy(keySelector: (E) * The returned map preserves the entry iteration order of the original channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.associateBy(keySelector: (E) -> K, valueTransform: (E) -> V): Map = +public suspend inline fun ReceiveChannel.associateBy(keySelector: (E) -> K, valueTransform: (E) -> V): Map = associateByTo(LinkedHashMap(), keySelector, valueTransform) /** @@ -764,9 +808,9 @@ public inline suspend fun ReceiveChannel.associateBy(keySelector: ( * If any two elements would have the same key returned by [keySelector] the last one gets added to the map. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.associateByTo(destination: M, keySelector: (E) -> K): M { +public suspend inline fun > ReceiveChannel.associateByTo(destination: M, keySelector: (E) -> K): M { consumeEach { destination.put(keySelector(it), it) } @@ -781,9 +825,9 @@ public inline suspend fun > ReceiveChannel.a * If any two elements would have the same key returned by [keySelector] the last one gets added to the map. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.associateByTo(destination: M, keySelector: (E) -> K, valueTransform: (E) -> V): M { +public suspend inline fun > ReceiveChannel.associateByTo(destination: M, keySelector: (E) -> K, valueTransform: (E) -> V): M { consumeEach { destination.put(keySelector(it), valueTransform(it)) } @@ -797,9 +841,9 @@ public inline suspend fun > ReceiveChannel> ReceiveChannel.associateTo(destination: M, transform: (E) -> Pair): M { +public suspend inline fun > ReceiveChannel.associateTo(destination: M, transform: (E) -> Pair): M { consumeEach { destination += transform(it) } @@ -811,7 +855,7 @@ public inline suspend fun > ReceiveChannel> ReceiveChannel.toChannel(destination: C): C { consumeEach { @@ -824,7 +868,7 @@ public suspend fun > ReceiveChannel.toChannel(destinati * Appends all elements to the given [destination] collection. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun > ReceiveChannel.toCollection(destination: C): C { consumeEach { @@ -837,7 +881,7 @@ public suspend fun > ReceiveChannel.toCollecti * Returns a [List] containing all elements. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.toList(): List = this.toMutableList() @@ -846,7 +890,7 @@ public suspend fun ReceiveChannel.toList(): List = * Returns a [Map] filled with all elements of this channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel>.toMap(): Map = toMap(LinkedHashMap()) @@ -855,7 +899,7 @@ public suspend fun ReceiveChannel>.toMap(): Map = * Returns a [MutableMap] filled with all elements of this channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun > ReceiveChannel>.toMap(destination: M): M { consumeEach { @@ -868,7 +912,7 @@ public suspend fun > ReceiveChannel> * Returns a [MutableList] filled with all elements of this channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.toMutableList(): MutableList = toCollection(ArrayList()) @@ -879,22 +923,22 @@ public suspend fun ReceiveChannel.toMutableList(): MutableList = * The returned set preserves the element iteration order of the original channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.toSet(): Set = - toCollection(LinkedHashSet()) + this.toMutableSet() /** * Returns a single channel of all elements from results of [transform] function being invoked on each element of original channel. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.flatMap(context: CoroutineContext = Unconfined, transform: suspend (E) -> ReceiveChannel): ReceiveChannel = - produce(context) { - consumeEach { - transform(it).toChannel(this) + produce(context, onCompletion = consumes()) { + for (e in this@flatMap) { + transform(e).toChannel(this) } } @@ -905,9 +949,9 @@ public fun ReceiveChannel.flatMap(context: CoroutineContext = Unconfin * The returned map preserves the entry iteration order of the keys produced from the original channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.groupBy(keySelector: (E) -> K): Map> = +public suspend inline fun ReceiveChannel.groupBy(keySelector: (E) -> K): Map> = groupByTo(LinkedHashMap(), keySelector) /** @@ -918,9 +962,9 @@ public inline suspend fun ReceiveChannel.groupBy(keySelector: (E) -> K * The returned map preserves the entry iteration order of the keys produced from the original channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.groupBy(keySelector: (E) -> K, valueTransform: (E) -> V): Map> = +public suspend inline fun ReceiveChannel.groupBy(keySelector: (E) -> K, valueTransform: (E) -> V): Map> = groupByTo(LinkedHashMap(), keySelector, valueTransform) /** @@ -930,9 +974,9 @@ public inline suspend fun ReceiveChannel.groupBy(keySelector: (E) - * @return The [destination] map. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun >> ReceiveChannel.groupByTo(destination: M, keySelector: (E) -> K): M { +public suspend inline fun >> ReceiveChannel.groupByTo(destination: M, keySelector: (E) -> K): M { consumeEach { val key = keySelector(it) val list = destination.getOrPut(key) { ArrayList() } @@ -949,9 +993,9 @@ public inline suspend fun >> ReceiveCh * @return The [destination] map. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun >> ReceiveChannel.groupByTo(destination: M, keySelector: (E) -> K, valueTransform: (E) -> V): M { +public suspend inline fun >> ReceiveChannel.groupByTo(destination: M, keySelector: (E) -> K, valueTransform: (E) -> V): M { consumeEach { val key = keySelector(it) val list = destination.getOrPut(key) { ArrayList() } @@ -965,11 +1009,11 @@ public inline suspend fun >> Receiv * to each element in the original channel. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -// todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 +// todo: mark transform with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.map(context: CoroutineContext = Unconfined, transform: suspend (E) -> R): ReceiveChannel = - produce(context) { + produce(context, onCompletion = consumes()) { consumeEach { send(transform(it)) } @@ -982,14 +1026,14 @@ public fun ReceiveChannel.map(context: CoroutineContext = Unconfined, * and returns the result of the transform applied to the element. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.mapIndexed(context: CoroutineContext = Unconfined, transform: suspend (index: Int, E) -> R): ReceiveChannel = - produce(context) { + produce(context, onCompletion = consumes()) { var index = 0 - consumeEach { - send(transform(index++, it)) + for (e in this@mapIndexed) { + send(transform(index++, e)) } } @@ -1000,7 +1044,7 @@ public fun ReceiveChannel.mapIndexed(context: CoroutineContext = Uncon * and returns the result of the transform applied to the element. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.mapIndexedNotNull(context: CoroutineContext = Unconfined, transform: suspend (index: Int, E) -> R?): ReceiveChannel = @@ -1013,9 +1057,9 @@ public fun ReceiveChannel.mapIndexedNotNull(context: CoroutineCo * and returns the result of the transform applied to the element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.mapIndexedNotNullTo(destination: C, transform: (index: Int, E) -> R?): C { +public suspend inline fun > ReceiveChannel.mapIndexedNotNullTo(destination: C, transform: (index: Int, E) -> R?): C { consumeEachIndexed { (index, element) -> transform(index, element)?.let { destination.add(it) } } @@ -1029,9 +1073,9 @@ public inline suspend fun > ReceiveChann * and returns the result of the transform applied to the element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.mapIndexedNotNullTo(destination: C, transform: (index: Int, E) -> R?): C { +public suspend inline fun > ReceiveChannel.mapIndexedNotNullTo(destination: C, transform: (index: Int, E) -> R?): C { consumeEachIndexed { (index, element) -> transform(index, element)?.let { destination.send(it) } } @@ -1045,9 +1089,9 @@ public inline suspend fun > ReceiveChannel.map * and returns the result of the transform applied to the element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.mapIndexedTo(destination: C, transform: (index: Int, E) -> R): C { +public suspend inline fun > ReceiveChannel.mapIndexedTo(destination: C, transform: (index: Int, E) -> R): C { var index = 0 consumeEach { destination.add(transform(index++, it)) @@ -1062,9 +1106,9 @@ public inline suspend fun > ReceiveChannel. * and returns the result of the transform applied to the element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.mapIndexedTo(destination: C, transform: (index: Int, E) -> R): C { +public suspend inline fun > ReceiveChannel.mapIndexedTo(destination: C, transform: (index: Int, E) -> R): C { var index = 0 consumeEach { destination.send(transform(index++, it)) @@ -1077,7 +1121,7 @@ public inline suspend fun > ReceiveChannel.mapIndexe * to each element in the original channel. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.mapNotNull(context: CoroutineContext = Unconfined, transform: suspend (E) -> R?): ReceiveChannel = @@ -1088,9 +1132,9 @@ public fun ReceiveChannel.mapNotNull(context: CoroutineContext = * and appends only the non-null results to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.mapNotNullTo(destination: C, transform: (E) -> R?): C { +public suspend inline fun > ReceiveChannel.mapNotNullTo(destination: C, transform: (E) -> R?): C { consumeEach { transform(it)?.let { destination.add(it) } } @@ -1102,9 +1146,9 @@ public inline suspend fun > ReceiveChann * and appends only the non-null results to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.mapNotNullTo(destination: C, transform: (E) -> R?): C { +public suspend inline fun > ReceiveChannel.mapNotNullTo(destination: C, transform: (E) -> R?): C { consumeEach { transform(it)?.let { destination.send(it) } } @@ -1116,9 +1160,9 @@ public inline suspend fun > ReceiveChannel.map * and appends the results to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.mapTo(destination: C, transform: (E) -> R): C { +public suspend inline fun > ReceiveChannel.mapTo(destination: C, transform: (E) -> R): C { consumeEach { destination.add(transform(it)) } @@ -1130,9 +1174,9 @@ public inline suspend fun > ReceiveChannel. * and appends the results to the given [destination]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.mapTo(destination: C, transform: (E) -> R): C { +public suspend inline fun > ReceiveChannel.mapTo(destination: C, transform: (E) -> R): C { consumeEach { destination.send(transform(it)) } @@ -1143,13 +1187,13 @@ public inline suspend fun > ReceiveChannel.mapTo(des * Returns a channel of [IndexedValue] for each element of the original channel. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public fun ReceiveChannel.withIndex(context: CoroutineContext = Unconfined): ReceiveChannel> = - produce(context) { + produce(context, onCompletion = consumes()) { var index = 0 - consumeEach { - send(IndexedValue(index++, it)) + for (e in this@withIndex) { + send(IndexedValue(index++, e)) } } @@ -1159,7 +1203,7 @@ public fun ReceiveChannel.withIndex(context: CoroutineContext = Unconfine * The elements in the resulting channel are in the same order as they were in the source channel. * * The operation is _intermediate_ and _stateful_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public fun ReceiveChannel.distinct(): ReceiveChannel = this.distinctBy { it } @@ -1171,16 +1215,16 @@ public fun ReceiveChannel.distinct(): ReceiveChannel = * The elements in the resulting channel are in the same order as they were in the source channel. * * The operation is _intermediate_ and _stateful_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ // todo: mark predicate with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.distinctBy(context: CoroutineContext = Unconfined, selector: suspend (E) -> K): ReceiveChannel = - produce(context) { + produce(context, onCompletion = consumes()) { val keys = HashSet() - consumeEach { - val k = selector(it) + for (e in this@distinctBy) { + val k = selector(e) if (k !in keys) { - send(it) + send(e) keys += k } } @@ -1192,23 +1236,18 @@ public fun ReceiveChannel.distinctBy(context: CoroutineContext = Uncon * The returned set preserves the element iteration order of the original channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public suspend fun ReceiveChannel.toMutableSet(): MutableSet { - val set = LinkedHashSet() - consumeEach { - set.add(it) - } - return set -} +public suspend fun ReceiveChannel.toMutableSet(): MutableSet = + toCollection(LinkedHashSet()) /** * Returns `true` if all elements match the given [predicate]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.all(predicate: (E) -> Boolean): Boolean { +public suspend inline fun ReceiveChannel.all(predicate: (E) -> Boolean): Boolean { consumeEach { if (!predicate(it)) return false } @@ -1219,7 +1258,7 @@ public inline suspend fun ReceiveChannel.all(predicate: (E) -> Boolean): * Returns `true` if channel has at least one element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.any(): Boolean = consume { @@ -1230,9 +1269,9 @@ public suspend fun ReceiveChannel.any(): Boolean = * Returns `true` if at least one element matches the given [predicate]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.any(predicate: (E) -> Boolean): Boolean { +public suspend inline fun ReceiveChannel.any(predicate: (E) -> Boolean): Boolean { consumeEach { if (predicate(it)) return true } @@ -1243,7 +1282,7 @@ public inline suspend fun ReceiveChannel.any(predicate: (E) -> Boolean): * Returns the number of elements in this channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.count(): Int { var count = 0 @@ -1255,9 +1294,9 @@ public suspend fun ReceiveChannel.count(): Int { * Returns the number of elements matching the given [predicate]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.count(predicate: (E) -> Boolean): Int { +public suspend inline fun ReceiveChannel.count(predicate: (E) -> Boolean): Int { var count = 0 consumeEach { if (predicate(it)) count++ @@ -1269,9 +1308,9 @@ public inline suspend fun ReceiveChannel.count(predicate: (E) -> Boolean) * Accumulates value starting with [initial] value and applying [operation] from left to right to current accumulator value and each element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.fold(initial: R, operation: (acc: R, E) -> R): R { +public suspend inline fun ReceiveChannel.fold(initial: R, operation: (acc: R, E) -> R): R { var accumulator = initial consumeEach { accumulator = operation(accumulator, it) @@ -1286,9 +1325,9 @@ public inline suspend fun ReceiveChannel.fold(initial: R, operation: ( * and the element itself, and calculates the next accumulator value. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.foldIndexed(initial: R, operation: (index: Int, acc: R, E) -> R): R { +public suspend inline fun ReceiveChannel.foldIndexed(initial: R, operation: (index: Int, acc: R, E) -> R): R { var index = 0 var accumulator = initial consumeEach { @@ -1301,9 +1340,9 @@ public inline suspend fun ReceiveChannel.foldIndexed(initial: R, opera * Returns the first element yielding the largest value of the given function or `null` if there are no elements. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.maxBy(selector: (E) -> R): E? = +public suspend inline fun > ReceiveChannel.maxBy(selector: (E) -> R): E? = consume { val iterator = iterator() if (!iterator.hasNext()) return null @@ -1324,7 +1363,7 @@ public inline suspend fun > ReceiveChannel.maxBy(selecto * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.maxWith(comparator: Comparator): E? = consume { @@ -1342,9 +1381,9 @@ public suspend fun ReceiveChannel.maxWith(comparator: Comparator): * Returns the first element yielding the smallest value of the given function or `null` if there are no elements. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun > ReceiveChannel.minBy(selector: (E) -> R): E? = +public suspend inline fun > ReceiveChannel.minBy(selector: (E) -> R): E? = consume { val iterator = iterator() if (!iterator.hasNext()) return null @@ -1365,7 +1404,7 @@ public inline suspend fun > ReceiveChannel.minBy(selecto * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.minWith(comparator: Comparator): E? = consume { @@ -1383,7 +1422,7 @@ public suspend fun ReceiveChannel.minWith(comparator: Comparator): * Returns `true` if the channel has no elements. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public suspend fun ReceiveChannel.none(): Boolean = consume { @@ -1394,9 +1433,9 @@ public suspend fun ReceiveChannel.none(): Boolean = * Returns `true` if no elements match the given [predicate]. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.none(predicate: (E) -> Boolean): Boolean { +public suspend inline fun ReceiveChannel.none(predicate: (E) -> Boolean): Boolean { consumeEach { if (predicate(it)) return false } @@ -1407,9 +1446,9 @@ public inline suspend fun ReceiveChannel.none(predicate: (E) -> Boolean): * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.reduce(operation: (acc: S, E) -> S): S = +public suspend inline fun ReceiveChannel.reduce(operation: (acc: S, E) -> S): S = consume { val iterator = this.iterator() if (!iterator.hasNext()) throw UnsupportedOperationException("Empty channel can't be reduced.") @@ -1427,9 +1466,10 @@ public inline suspend fun ReceiveChannel.reduce(operation: (acc: S * and the element itself and calculates the next accumulator value. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.reduceIndexed(operation: (index: Int, acc: S, E) -> S): S = +// todo: mark operation with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 +public suspend inline fun ReceiveChannel.reduceIndexed(operation: (index: Int, acc: S, E) -> S): S = consume { val iterator = this.iterator() if (!iterator.hasNext()) throw UnsupportedOperationException("Empty channel can't be reduced.") @@ -1445,9 +1485,9 @@ public inline suspend fun ReceiveChannel.reduceIndexed(operation: * Returns the sum of all values produced by [selector] function applied to each element in the channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.sumBy(selector: (E) -> Int): Int { +public suspend inline fun ReceiveChannel.sumBy(selector: (E) -> Int): Int { var sum = 0 consumeEach { sum += selector(it) @@ -1459,9 +1499,9 @@ public inline suspend fun ReceiveChannel.sumBy(selector: (E) -> Int): Int * Returns the sum of all values produced by [selector] function applied to each element in the channel. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.sumByDouble(selector: (E) -> Double): Double { +public suspend inline fun ReceiveChannel.sumByDouble(selector: (E) -> Double): Double { var sum = 0.0 consumeEach { sum += selector(it) @@ -1473,7 +1513,7 @@ public inline suspend fun ReceiveChannel.sumByDouble(selector: (E) -> Dou * Returns an original collection containing all the non-`null` elements, throwing an [IllegalArgumentException] if there are any `null` elements. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ public fun ReceiveChannel.requireNoNulls(): ReceiveChannel = map { it ?: throw IllegalArgumentException("null element found in $this.") } @@ -1484,9 +1524,9 @@ public fun ReceiveChannel.requireNoNulls(): ReceiveChannel = * while *second* list contains elements for which [predicate] yielded `false`. * * The operation is _terminal_. - * This function [consumes][consume] all elements of the original [ReceiveChannel]. + * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -public inline suspend fun ReceiveChannel.partition(predicate: (E) -> Boolean): Pair, List> { +public suspend inline fun ReceiveChannel.partition(predicate: (E) -> Boolean): Pair, List> { val first = ArrayList() val second = ArrayList() consumeEach { @@ -1504,7 +1544,7 @@ public inline suspend fun ReceiveChannel.partition(predicate: (E) -> Bool * Resulting channel has length of shortest input channel. * * The operation is _intermediate_ and _stateless_. - * This function [consumes][consume] all elements of both the original [ReceiveChannel] and the `other` one. + * This function [consumes][ReceiveChannel.consume] all elements of both the original [ReceiveChannel] and the `other` one. */ public infix fun ReceiveChannel.zip(other: ReceiveChannel): ReceiveChannel> = zip(other) { t1, t2 -> t1 to t2 } @@ -1515,15 +1555,15 @@ public infix fun ReceiveChannel.zip(other: ReceiveChannel): Receive * The operation is _intermediate_ and _stateless_. * This function [consumes][consume] all elements of both the original [ReceiveChannel] and the `other` one. */ +// todo: mark transform with crossinline modifier when it is supported: https://youtrack.jetbrains.com/issue/KT-19159 public fun ReceiveChannel.zip(other: ReceiveChannel, context: CoroutineContext = Unconfined, transform: (a: E, b: R) -> V): ReceiveChannel = - produce(context) { - other.consume { - val otherIterator = other.iterator() - this@zip.consumeEach { element1 -> - if (!otherIterator.hasNext()) return@consumeEach - val element2 = otherIterator.next() - send(transform(element1, element2)) - } + produce(context, onCompletion = consumesAll(this, other)) { + val otherIterator = other.iterator() + this@zip.consumeEach { element1 -> + if (!otherIterator.hasNext()) return@consumeEach + val element2 = otherIterator.next() + send(transform(element1, element2)) } } + diff --git a/core/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 index 61e6a6f9c2..1565ecde9d 100644 --- a/core/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 @@ -70,21 +70,33 @@ interface ProducerJob : ReceiveChannel, Job { * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param capacity capacity of the channel's buffer (no buffer by default). * @param parent explicitly specifies the parent job, overrides job from the [context] (if any).* + * @param onCompletion optional completion handler for the producer coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public fun produce( context: CoroutineContext = DefaultDispatcher, capacity: Int = 0, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend ProducerScope.() -> Unit ): ReceiveChannel { val channel = Channel(capacity) val newContext = newCoroutineContext(context, parent) val coroutine = ProducerCoroutine(newContext, channel) + if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) coroutine.start(CoroutineStart.DEFAULT, coroutine, block) return coroutine } +/** @suppress **Deprecated**: Binary compatibility */ +@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) +public fun produce( + context: CoroutineContext = DefaultDispatcher, + capacity: Int = 0, + parent: Job? = null, + block: suspend ProducerScope.() -> Unit +): ReceiveChannel = produce(context, capacity, parent, block = block) + /** @suppress **Deprecated**: Binary compatibility */ @Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) public fun produce( diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsConsumeTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsConsumeTest.kt new file mode 100644 index 0000000000..18c3234d5e --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsConsumeTest.kt @@ -0,0 +1,911 @@ +/* + * 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.* +import kotlin.coroutines.experimental.* +import kotlin.test.* + +/** + * Tests that various operators on channels properly consume (close) their source channels. + */ +class ChannelsConsumeTest { + private val sourceList = (1..10).toList() + + // test source with numbers 1..10 + private fun testSource() = produce { + for (i in sourceList) { + send(i) + } + } + + @Test + fun testConsume() { + checkTerminal { + consume { + assertEquals(1, receive()) + } + } + } + + @Test + fun testConsumeEach() { + checkTerminal { + var sum = 0 + consumeEach { sum += it } + assertEquals(55, sum) + } + } + + @Test + fun testConsumeEachIndexed() { + checkTerminal { + var sum = 0 + consumeEachIndexed { (index, i) -> sum += index * i } + assertEquals(330, sum) + } + } + + @Test + fun testElementAt() { + checkTerminal { + assertEquals(2, elementAt(1)) + } + checkTerminal(expected = { it is IndexOutOfBoundsException }) { + elementAt(10) + } + } + + @Test + fun testElementAtOrElse() { + checkTerminal { + assertEquals(3, elementAtOrElse(2) { error("Cannot happen") }) + } + checkTerminal { + assertEquals(-23, elementAtOrElse(10) { -23 }) + } + } + + @Test + fun testElementOrNull() { + checkTerminal { + assertEquals(4, elementAtOrNull(3)) + } + checkTerminal { + assertEquals(null, elementAtOrNull(10)) + } + } + + @Test + fun testFind() { + checkTerminal { + assertEquals(3, find { it % 3 == 0 }) + } + } + + @Test + fun testFindLast() { + checkTerminal { + assertEquals(9, findLast { it % 3 == 0 }) + } + } + + @Test + fun testFirst() { + checkTerminal { + assertEquals(1, first()) + } + } + + @Test + fun testFirstPredicate() { + checkTerminal { + assertEquals(3, first { it % 3 == 0 }) + } + checkTerminal(expected = { it is NoSuchElementException }) { + first { it > 10 } + } + } + + @Test + fun testFirstOrNull() { + checkTerminal { + assertEquals(1, firstOrNull()) + } + } + + @Test + fun testFirstOrNullPredicate() { + checkTerminal { + assertEquals(3, firstOrNull { it % 3 == 0 }) + } + checkTerminal { + assertEquals(null, firstOrNull { it > 10 }) + } + } + + @Test + fun testIndexOf() { + checkTerminal { + assertEquals(2, indexOf(3)) + } + checkTerminal { + assertEquals(-1, indexOf(11)) + } + } + + @Test + fun testIndexOfFirst() { + checkTerminal { + assertEquals(2, indexOfFirst { it % 3 == 0 }) + } + checkTerminal { + assertEquals(-1, indexOfFirst { it > 10 }) + } + } + + @Test + fun testIndexOfLast() { + checkTerminal { + assertEquals(8, indexOfLast { it % 3 == 0 }) + } + checkTerminal { + assertEquals(-1, indexOfLast { it > 10 }) + } + } + + @Test + fun testLast() { + checkTerminal { + assertEquals(10, last()) + } + } + + @Test + fun testLastPredicate() { + checkTerminal { + assertEquals(9, last { it % 3 == 0 }) + } + checkTerminal(expected = { it is NoSuchElementException }) { + last { it > 10 } + } + } + + @Test + fun testLastIndexOf() { + checkTerminal { + assertEquals(8, lastIndexOf(9)) + } + } + + @Test + fun testLastOrNull() { + checkTerminal { + assertEquals(10, lastOrNull()) + } + } + + @Test + fun testLastOrNullPredicate() { + checkTerminal { + assertEquals(9, lastOrNull { it % 3 == 0 }) + } + checkTerminal { + assertEquals(null, lastOrNull { it > 10 }) + } + } + + @Test + fun testSingle() { + checkTerminal(expected = { it is IllegalArgumentException }) { + single() + } + } + + @Test + fun testSinglePredicate() { + checkTerminal { + assertEquals(7, single { it % 7 == 0 }) + } + checkTerminal(expected = { it is IllegalArgumentException }) { + single { it % 3 == 0 } + } + checkTerminal(expected = { it is NoSuchElementException }) { + single { it > 10 } + } + } + + @Test + fun testSingleOrNull() { + checkTerminal { + assertEquals(null, singleOrNull()) + } + } + + @Test + fun testSingleOrNullPredicate() { + checkTerminal { + assertEquals(7, singleOrNull { it % 7 == 0 }) + } + checkTerminal { + assertEquals(null, singleOrNull { it % 3 == 0 }) + } + checkTerminal { + assertEquals(null, singleOrNull { it > 10 }) + } + } + + @Test + fun testDrop() { + checkTransform(sourceList.drop(3)) { ctx -> + drop(3, ctx) + } + } + + @Test + fun testDropWhile() { + checkTransform(sourceList.dropWhile { it < 4}) { ctx -> + dropWhile(ctx) { it < 4 } + } + } + + @Test + fun testFilter() { + checkTransform(sourceList.filter { it % 2 == 0 }) { ctx -> + filter(ctx) { it % 2 == 0 } + } + } + + @Test + fun testFilterIndexed() { + checkTransform(sourceList.filterIndexed { index, _ -> index % 2 == 0 }) { ctx -> + filterIndexed(ctx) { index, _ -> index % 2 == 0 } + } + } + + @Test + fun testFilterIndexedToCollection() { + checkTerminal { + val list = mutableListOf() + filterIndexedTo(list) { index, _ -> index % 2 == 0 } + assertEquals(listOf(1, 3, 5, 7, 9), list) + } + } + + @Test + fun testFilterIndexedToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + filterIndexedTo(channel) { index, _ -> index % 2 == 0 } + channel.close() + assertEquals(listOf(1, 3, 5, 7, 9), result.await()) + } + } + + @Test + fun testFilterNot() { + checkTransform(sourceList.filterNot { it % 2 == 0 }) { ctx -> + filterNot(ctx) { it % 2 == 0 } + } + } + + @Test + fun testFilterNotNullToCollection() { + checkTerminal { + val list = mutableListOf() + filterNotNullTo(list) + assertEquals((1..10).toList(), list) + } + } + + @Test + fun testFilterNotNullToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + filterNotNullTo(channel) + channel.close() + assertEquals((1..10).toList(), result.await()) + } + } + + @Test + fun testFilterNotToCollection() { + checkTerminal { + val list = mutableListOf() + filterNotTo(list) { it % 2 == 0 } + assertEquals(listOf(1, 3, 5, 7, 9), list) + } + } + + @Test + fun testFilterNotToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + filterNotTo(channel) { it % 2 == 0 } + channel.close() + assertEquals(listOf(1, 3, 5, 7, 9), result.await()) + } + } + + @Test + fun testFilterToCollection() { + checkTerminal { + val list = mutableListOf() + filterTo(list) { it % 2 == 0 } + assertEquals(listOf(2, 4, 6, 8, 10), list) + } + } + + @Test + fun testFilterToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + filterTo(channel) { it % 2 == 0 } + channel.close() + assertEquals(listOf(2, 4, 6, 8, 10), result.await()) + } + } + + @Test + fun testTake() { + checkTransform(sourceList.take(3)) { ctx -> + take(3, ctx) + } + } + + @Test + fun testTakeWhile() { + checkTransform(sourceList.takeWhile { it < 4 }) { ctx -> + takeWhile(ctx) { it < 4 } + } + } + + @Test + fun testAssociate() { + checkTerminal { + assertEquals(sourceList.associate { it to it.toString() }, associate { it to it.toString() }) + } + } + + @Test + fun testAssociateBy() { + checkTerminal { + assertEquals(sourceList.associateBy { it.toString() }, associateBy { it.toString() }) + } + } + + @Test + fun testAssociateByTwo() { + checkTerminal { + assertEquals(sourceList.associateBy({ it.toString() }, { it + 1}), associateBy({ it.toString() }, { it + 1})) + } + } + + @Test + fun testAssociateByToMap() { + checkTerminal { + val map = mutableMapOf() + associateByTo(map) { it.toString() } + assertEquals(sourceList.associateBy { it.toString() }, map) + } + } + + @Test + fun testAssociateByTwoToMap() { + checkTerminal { + val map = mutableMapOf() + associateByTo(map, { it.toString() }, { it + 1}) + assertEquals(sourceList.associateBy({ it.toString() }, { it + 1}), map) + } + } + + @Test + fun testAssociateToMap() { + checkTerminal { + val map = mutableMapOf() + associateTo(map) { it to it.toString() } + assertEquals(sourceList.associate { it to it.toString() }, map) + } + } + + @Test + fun testToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + toChannel(channel) + channel.close() + assertEquals(sourceList, result.await()) + } + } + + @Test + fun testToCollection() { + checkTerminal { + val list = mutableListOf() + toCollection(list) + assertEquals(sourceList, list) + } + } + + @Test + fun testToList() { + checkTerminal { + val list = toList() + assertEquals(sourceList, list) + } + } + + @Test + fun testToMap() { + checkTerminal { + val map = map { it to it.toString() }.toMap() + assertEquals(sourceList.map { it to it.toString() }.toMap(), map) + } + } + + @Test + fun testToMapWithMap() { + checkTerminal { + val map = mutableMapOf() + map { it to it.toString() }.toMap(map) + assertEquals(sourceList.map { it to it.toString() }.toMap(), map) + } + } + + @Test + fun testToMutableList() { + checkTerminal { + val list = toMutableList() + assertEquals(sourceList, list) + } + } + + @Test + fun testToSet() { + checkTerminal { + val set = toSet() + assertEquals(sourceList.toSet(), set) + } + } + + @Test + fun testFlatMap() { + checkTransform(sourceList.flatMap { listOf("A$it", "B$it") }) { ctx -> + flatMap(ctx) { + produce { + send("A$it") + send("B$it") + } + } + } + } + + @Test + fun testGroupBy() { + checkTerminal { + val map = groupBy { it % 2 } + assertEquals(sourceList.groupBy { it % 2 }, map) + } + } + + @Test + fun testGroupByTwo() { + checkTerminal { + val map = groupBy({ it % 2 }, { it.toString() }) + assertEquals(sourceList.groupBy({ it % 2 }, { it.toString() }), map) + } + } + + @Test + fun testGroupByTo() { + checkTerminal { + val map = mutableMapOf>() + groupByTo(map) { it % 2 } + assertEquals(sourceList.groupBy { it % 2 }, map) + } + } + + @Test + fun testGroupByToTwo() { + checkTerminal { + val map = mutableMapOf>() + groupByTo(map, { it % 2 }, { it.toString() }) + assertEquals(sourceList.groupBy({ it % 2 }, { it.toString() }), map) + } + } + + @Test + fun testMap() { + checkTransform(sourceList.map { it.toString() }) { ctx -> + map(ctx) { it.toString() } + } + } + + @Test + fun testMapIndexed() { + checkTransform(sourceList.mapIndexed { index, v -> "$index$v" }) { ctx -> + mapIndexed(ctx) { index, v -> "$index$v" } + } + } + + @Test + fun testMapIndexedNotNull() { + checkTransform(sourceList.mapIndexedNotNull { index, v -> "$index$v".takeIf { v % 2 == 0 } }) { ctx -> + mapIndexedNotNull(ctx) { index, v -> "$index$v".takeIf { v % 2 == 0 } } + } + } + + @Test + fun testMapIndexedNotNullToCollection() { + checkTerminal { + val list = mutableListOf() + mapIndexedNotNullTo(list) { index, v -> "$index$v".takeIf { v % 2 == 0 } } + assertEquals(sourceList.mapIndexedNotNull { index, v -> "$index$v".takeIf { v % 2 == 0 } }, list) + } + } + + @Test + fun testMapIndexedNotNullToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + mapIndexedNotNullTo(channel) { index, v -> "$index$v".takeIf { v % 2 == 0 } } + channel.close() + assertEquals(sourceList.mapIndexedNotNull { index, v -> "$index$v".takeIf { v % 2 == 0 } }, result.await()) + } + } + + @Test + fun testMapIndexedToCollection() { + checkTerminal { + val list = mutableListOf() + mapIndexedTo(list) { index, v -> "$index$v" } + assertEquals(sourceList.mapIndexed { index, v -> "$index$v" }, list) + } + } + + @Test + fun testMapIndexedToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + mapIndexedTo(channel) { index, v -> "$index$v" } + channel.close() + assertEquals(sourceList.mapIndexed { index, v -> "$index$v" }, result.await()) + } + } + + @Test + fun testMapNotNull() { + checkTransform(sourceList.mapNotNull { (it + 3).takeIf { it % 2 == 0 } }) { ctx -> + mapNotNull(ctx) { (it + 3).takeIf { it % 2 == 0 } } + } + } + + @Test + fun testMapNotNullToCollection() { + checkTerminal { + val list = mutableListOf() + mapNotNullTo(list) { (it + 3).takeIf { it % 2 == 0 } } + assertEquals(sourceList.mapNotNull { (it + 3).takeIf { it % 2 == 0 } }, list) + } + } + + @Test + fun testMapNotNullToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + mapNotNullTo(channel) { (it + 3).takeIf { it % 2 == 0 } } + channel.close() + assertEquals(sourceList.mapNotNull { (it + 3).takeIf { it % 2 == 0 } }, result.await()) + } + } + + @Test + fun testMapToCollection() { + checkTerminal { + val list = mutableListOf() + mapTo(list) { it + 3 } + assertEquals(sourceList.map { it + 3 }, list) + } + } + + @Test + fun testMapToChannel() { + checkTerminal { + val channel = Channel() + val result = async { channel.toList() } + mapTo(channel) { it + 3 } + channel.close() + assertEquals(sourceList.map { it + 3 }, result.await()) + } + } + + @Test + fun testWithIndex() { + checkTransform(sourceList.withIndex().toList()) { ctx -> + withIndex(ctx) + } + } + + @Test + fun testDistinctBy() { + checkTransform(sourceList.distinctBy { it / 2 }) { ctx -> + distinctBy(ctx) { it / 2 } + } + } + + @Test + fun testToMutableSet() { + checkTerminal { + val set = toMutableSet() + assertEquals(sourceList.toSet(), set) + } + } + + @Test + fun testAll() { + checkTerminal { + val all = all { it < 11 } + assertEquals(sourceList.all { it < 11 }, all) + } + } + + @Test + fun testAny() { + checkTerminal { + val any = any() + assertEquals(sourceList.any(), any) + } + } + + @Test + fun testAnyPredicate() { + checkTerminal { + val any = any { it % 3 == 0 } + assertEquals(sourceList.any { it % 3 == 0 }, any) + } + } + + @Test + fun testCount() { + checkTerminal { + val c = count() + assertEquals(sourceList.count(), c) + } + } + + @Test + fun testCountPredicate() { + checkTerminal { + val c = count { it % 3 == 0 } + assertEquals(sourceList.count { it % 3 == 0 }, c) + } + } + + @Test + fun testFold() { + checkTerminal { + val c = fold(1) { a, b -> a + b } + assertEquals(sourceList.fold(1) { a, b -> a + b }, c) + } + } + + @Test + fun testFoldIndexed() { + checkTerminal { + val c = foldIndexed(1) { i, a, b -> i * a + b } + assertEquals(sourceList.foldIndexed(1) { i, a, b -> i * a + b }, c) + } + } + + @Test + fun testMaxBy() { + checkTerminal { + val c = maxBy { it % 3 } + assertEquals(sourceList.maxBy { it % 3 }, c) + } + } + + @Test + fun testMaxWith() { + checkTerminal { + val c = maxWith(compareBy { it % 3 }) + assertEquals(sourceList.maxWith(compareBy { it % 3 }), c) + } + } + + @Test + fun testMinBy() { + checkTerminal { + val c = maxBy { it % 3 } + assertEquals(sourceList.maxBy { it % 3 }, c) + } + } + + @Test + fun testMinWith() { + checkTerminal { + val c = maxWith(compareBy { it % 3 }) + assertEquals(sourceList.maxWith(compareBy { it % 3 }), c) + } + } + + @Test + fun testNone() { + checkTerminal { + val none = none() + assertEquals(sourceList.none(), none) + } + } + + @Test + fun testNonePredicate() { + checkTerminal { + val none = none { it > 10 } + assertEquals(sourceList.none { it > 10 }, none) + } + } + + @Test + fun testReduce() { + checkTerminal { + val c = reduce { a, b -> a + b } + assertEquals(sourceList.reduce { a, b -> a + b }, c) + } + } + + @Test + fun testReduceIndexed() { + checkTerminal { + val c = reduceIndexed { i, a, b -> i * a + b } + assertEquals(sourceList.reduceIndexed { i, a, b -> i * a + b }, c) + } + } + + @Test + fun testSubBy() { + checkTerminal { + val c = sumBy { it } + assertEquals(sourceList.sumBy { it }, c) + } + } + + @Test + fun testSubByDouble() { + checkTerminal { + val c = sumByDouble { it.toDouble() } + assertEquals(sourceList.sumByDouble { it.toDouble() }, c) + } + } + + @Test + fun testPartition() { + checkTerminal { + val pair = partition { it % 2 == 0 } + assertEquals(sourceList.partition { it % 2 == 0 }, pair) + } + } + + @Test + fun testZip() { + val expect = sourceList.zip(sourceList) { a, b -> a + 2 * b } + checkTransform(expect) { ctx -> + zip(testSource(), ctx) { a, b -> a + 2*b } + } + checkTransform(expect) { ctx -> + testSource().zip(this, ctx) { a, b -> a + 2*b } + } + } + + // ------------------ + + private fun checkTerminal( + expected: ((Throwable?) -> Unit)? = null, + terminal: suspend ReceiveChannel.() -> Unit + ) { + checkTerminalCompletion(expected, terminal) + checkTerminalCancellation(expected, terminal) + } + + private fun checkTerminalCompletion( + expected: ((Throwable?) -> Unit)? = null, + terminal: suspend ReceiveChannel.() -> Unit + ) { + val src = testSource() + runBlocking { + try { + // terminal operation + terminal(src) + // source must be cancelled at the end of terminal op + assertTrue(src.isClosedForReceive, "Source must be closed") + if (expected != null) error("Exception was expected") + } catch (e: Throwable) { + if (expected == null) throw e + expected(e) + } + } + } + + private fun checkTerminalCancellation( + expected: ((Throwable?) -> Unit)? = null, + terminal: suspend ReceiveChannel.() -> Unit + ) { + val src = testSource() + runBlocking { + // terminal operation in a separate async context started until the first suspension + val d = async(coroutineContext, start = CoroutineStart.UNDISPATCHED) { + terminal(src) + } + // then cancel it + d.cancel() + // and try to get it's result + try { + d.await() + } catch (e: CancellationException) { + // ok -- was cancelled + } catch (e: Throwable) { + // if threw a different exception -- must be an expected one + if (expected == null) throw e + expected(e) + } + } + // source must be cancelled at the end of terminal op even if it was cancelled while in process + assertTrue(src.isClosedForReceive, "Source must be closed") + } + + private fun checkTransform( + expect: List, + transform: ReceiveChannel.(CoroutineContext) -> ReceiveChannel + ) { + // check for varying number of received elements from the channel + for (nReceive in 0..expect.size) { + checkTransform(nReceive, expect, transform) + } + } + + private fun checkTransform( + nReceive: Int, + expect: List, + transform: ReceiveChannel.(CoroutineContext) -> ReceiveChannel + ) { + val src = testSource() + runBlocking { + // transform + val res = transform(src, coroutineContext) + // receive nReceive elements from the result + repeat(nReceive) { i -> + assertEquals(expect[i], res.receive()) + } + if (nReceive < expect.size) { + // then cancel + res.cancel() + } else { + // then check that result is closed + assertEquals(null, res.receiveOrNull(), "Result has unexpected values") + } + } + // source must be cancelled when runBlocking processes all the scheduled stuff + assertTrue(src.isClosedForReceive, "Source must be closed") + } +} \ No newline at end of file From fe8ba6b576af901267634c622f8f304ae6b6164a Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Tue, 13 Mar 2018 17:34:29 +0300 Subject: [PATCH 02/61] `onCompletion` added to `launch`, `async`, and `actor` --- .../coroutines/experimental/CommonBuilders.kt | 3 ++- .../coroutines/experimental/CommonDeferred.kt | 3 ++- .../kotlinx/coroutines/experimental/Builders.kt | 12 ++++++++++++ .../kotlinx/coroutines/experimental/Deferred.kt | 14 +++++++++++++- .../coroutines/experimental/channels/Actor.kt | 13 +++++++++++++ .../kotlinx/coroutines/experimental/Builders.kt | 11 +++++++---- .../kotlinx/coroutines/experimental/Deferred.kt | 5 ++++- 7 files changed, 53 insertions(+), 8 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt index 924910b3fa..7522850a12 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt @@ -16,13 +16,14 @@ package kotlinx.coroutines.experimental -import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.* @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") public expect fun launch( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> Unit ): Job diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDeferred.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDeferred.kt index fbabf3de60..3c6786c833 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDeferred.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDeferred.kt @@ -16,7 +16,7 @@ package kotlinx.coroutines.experimental -import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.* public expect interface Deferred : Job { public val isCompletedExceptionally: Boolean @@ -30,5 +30,6 @@ public expect fun async( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> T ): Deferred \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt index 7b79f46911..497ba9073c 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt @@ -53,22 +53,34 @@ import kotlin.coroutines.experimental.intrinsics.* * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). + * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public actual fun launch( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context, parent) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) + if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) coroutine.start(start, coroutine, block) return coroutine } +/** @suppress **Deprecated**: Binary compatibility */ +@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) +public fun launch( + context: CoroutineContext = DefaultDispatcher, + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + block: suspend CoroutineScope.() -> Unit +): Job = launch(context, start, parent, block = block) + /** @suppress **Deprecated**: Binary compatibility */ @Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) public fun launch( diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt index fd0edfcc44..1edf519321 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt @@ -157,23 +157,35 @@ public actual interface Deferred : Job { * * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. - * @param parent explicitly specifies the parent job, overrides job from the [context] (if any).* + * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). + * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public actual fun async( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> T ): Deferred { val newContext = newCoroutineContext(context, parent) val coroutine = if (start.isLazy) LazyDeferredCoroutine(newContext, block) else DeferredCoroutine(newContext, active = true) + if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) coroutine.start(start, coroutine, block) return coroutine } +/** @suppress **Deprecated**: Binary compatibility */ +@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) +public fun async( + context: CoroutineContext = DefaultDispatcher, + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + block: suspend CoroutineScope.() -> T +): Deferred = async(context, start, parent, block = block) + /** @suppress **Deprecated**: Binary compatibility */ @Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) public fun async( diff --git a/core/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 index 5594cdee42..bb28f358b7 100644 --- a/core/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 @@ -116,6 +116,7 @@ interface ActorJob : SendChannel { * @param capacity capacity of the channel's buffer (no buffer by default). * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. * @param parent explicitly specifies the parent job, overrides job from the [context] (if any).* + * @param onCompletion optional completion handler for the actor coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public fun actor( @@ -123,6 +124,7 @@ public fun actor( capacity: Int = 0, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend ActorScope.() -> Unit ): SendChannel { val newContext = newCoroutineContext(context, parent) @@ -130,10 +132,21 @@ public fun actor( val coroutine = if (start.isLazy) LazyActorCoroutine(newContext, channel, block) else ActorCoroutine(newContext, channel, active = true) + if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) coroutine.start(start, coroutine, block) return coroutine } +/** @suppress **Deprecated**: Binary compatibility */ +@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) +public fun actor( + context: CoroutineContext = DefaultDispatcher, + capacity: Int = 0, + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + block: suspend ActorScope.() -> Unit +): SendChannel = actor(context, capacity, start, parent, block = block) + /** @suppress **Deprecated**: Binary compatibility */ @Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) public fun actor( diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt index a2af761b0a..bf89cbe63e 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt @@ -50,18 +50,21 @@ import kotlin.coroutines.experimental.intrinsics.* * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). + * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public actual fun launch( - context: CoroutineContext = DefaultDispatcher, - start: CoroutineStart = CoroutineStart.DEFAULT, - parent: Job? = null, - block: suspend CoroutineScope.() -> Unit + context: CoroutineContext = DefaultDispatcher, + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + onCompletion: CompletionHandler? = null, + block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context, parent) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) + if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) coroutine.start(start, coroutine, block) return coroutine } diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt index 7625a74247..2e6f311988 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt @@ -140,19 +140,22 @@ public actual interface Deferred : Job { * * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. - * @param parent explicitly specifies the parent job, overrides job from the [context] (if any).* + * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). + * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public actual fun async( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> T ): Deferred { val newContext = newCoroutineContext(context, parent) val coroutine = if (start.isLazy) LazyDeferredCoroutine(newContext, block) else DeferredCoroutine(newContext, active = true) + if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) coroutine.start(start, coroutine, block) return coroutine } From a4b5693c2df018558b3e99f95a30922a588847c2 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Tue, 13 Mar 2018 17:59:48 +0300 Subject: [PATCH 03/61] `onCompletion` added to `promise` and `future` (JDK8 and Guava) --- .../experimental/guava/ListenableFuture.kt | 21 ++++++++++------ .../coroutines/experimental/future/Future.kt | 24 ++++++++++++------- .../coroutines/experimental/Promise.kt | 9 +++---- 3 files changed, 34 insertions(+), 20 deletions(-) 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 b8d8ff5628..2eb4c8eb10 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 @@ -16,14 +16,9 @@ package kotlinx.coroutines.experimental.guava -import com.google.common.util.concurrent.AbstractFuture -import com.google.common.util.concurrent.FutureCallback -import com.google.common.util.concurrent.Futures -import com.google.common.util.concurrent.ListenableFuture +import com.google.common.util.concurrent.* import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.ContinuationInterceptor -import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.* /** * Starts new coroutine and returns its results an an implementation of [ListenableFuture]. @@ -50,12 +45,14 @@ import kotlin.coroutines.experimental.CoroutineContext * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). + * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public fun future( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> T ): ListenableFuture { require(!start.isLazy) { "$start start is not supported" } @@ -63,10 +60,20 @@ public fun future( val job = Job(newContext[Job]) val future = ListenableFutureCoroutine(newContext + job) job.cancelFutureOnCompletion(future) + if (onCompletion != null) job.invokeOnCompletion(handler = onCompletion) start(block, receiver=future, completion=future) // use the specified start strategy return future } +/** @suppress **Deprecated**: Binary compatibility */ +@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) +public fun future( + context: CoroutineContext = DefaultDispatcher, + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + block: suspend CoroutineScope.() -> T +): ListenableFuture = future(context, start, parent, block = block) + /** @suppress **Deprecated**: Binary compatibility */ @Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) public fun future( 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 8bb84627eb..e2d5117f54 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 @@ -17,15 +17,9 @@ package kotlinx.coroutines.experimental.future import kotlinx.coroutines.experimental.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CompletionStage -import java.util.concurrent.ExecutionException -import java.util.concurrent.Future -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 +import java.util.concurrent.* +import java.util.function.* +import kotlin.coroutines.experimental.* /** * Starts new coroutine and returns its result as an implementation of [CompletableFuture]. @@ -52,12 +46,14 @@ import kotlin.coroutines.experimental.suspendCoroutine * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). + * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public fun future( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> T ): CompletableFuture { require(!start.isLazy) { "$start start is not supported" } @@ -66,10 +62,20 @@ public fun future( val future = CompletableFutureCoroutine(newContext + job) job.cancelFutureOnCompletion(future) future.whenComplete { _, exception -> job.cancel(exception) } + if (onCompletion != null) job.invokeOnCompletion(handler = onCompletion) start(block, receiver=future, completion=future) // use the specified start strategy return future } +/** @suppress **Deprecated**: Binary compatibility */ +@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) +public fun future( + context: CoroutineContext = DefaultDispatcher, + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + block: suspend CoroutineScope.() -> T +): CompletableFuture = future(context, start, parent, block = block) + /** @suppress **Deprecated**: Binary compatibility */ @Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) public fun future( diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Promise.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Promise.kt index 621a737755..9904fe7e76 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Promise.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Promise.kt @@ -16,9 +16,8 @@ package kotlinx.coroutines.experimental -import kotlin.coroutines.experimental.ContinuationInterceptor -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.js.Promise +import kotlin.coroutines.experimental.* +import kotlin.js.* /** * Starts new coroutine and returns its result as an implementation of [Promise]. @@ -37,15 +36,17 @@ import kotlin.js.Promise * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). + * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ public fun promise( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, + onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> T ): Promise = - async(context, start, parent, block).asPromise() + async(context, start, parent, onCompletion, block = block).asPromise() /** * Converts this deferred value to the instance of [Promise]. From ab66afc3f7fa6abc05f4ea897b4b97ad75419090 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Tue, 20 Feb 2018 18:55:39 +0300 Subject: [PATCH 04/61] Kotlin 1.2.30 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9b8156238b..efab101cad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ version = 0.22.5-SNAPSHOT -kotlin_version = 1.2.21 +kotlin_version = 1.2.30 junit_version = 4.12 atomicFU_version = 0.9.2 html_version = 0.6.8 From 9fe5f46f58f01f9e1ebd144e34e2107c6d0b4038 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 21 Feb 2018 19:05:52 +0300 Subject: [PATCH 05/61] Deprecated CoroutineScope.coroutineContext which is replaced with top-level function from stdlib. --- .../experimental/AbstractCoroutine.kt | 1 + .../experimental/CommonCoroutineScope.kt | 24 ----- .../coroutines/experimental/CommonJob.kt | 1 + .../coroutines/experimental/CoroutineScope.kt | 31 +++--- .../experimental/AbstractCoroutineTest.kt | 3 + .../experimental/CommonAsyncLazyTest.kt | 1 + .../experimental/CommonAsyncTest.kt | 1 + .../CommonAtomicCancellationTest.kt | 1 + .../CommonCompletableDeferredTest.kt | 1 + .../CommonCoroutineExceptionHandlerTest.kt | 1 + .../experimental/CommonCoroutinesTest.kt | 1 + .../coroutines/experimental/CommonJobTest.kt | 1 + .../experimental/CommonLaunchLazyTest.kt | 1 + .../experimental/CommonWithContextTest.kt | 3 +- .../CommonWithTimeoutOrNullTest.kt | 1 + .../experimental/CommonWithTimeoutTest.kt | 1 + .../coroutines/experimental/Builders.kt | 3 +- .../coroutines/experimental/Deferred.kt | 8 +- .../kotlinx/coroutines/experimental/Job.kt | 23 +++- .../coroutines/experimental/selects/Select.kt | 2 +- .../test/kotlin/guide/example-channel-05.kt | 2 +- .../test/kotlin/guide/example-channel-07.kt | 1 + .../test/kotlin/guide/example-channel-08.kt | 1 + .../test/kotlin/guide/example-channel-09.kt | 1 + .../test/kotlin/guide/example-compose-01.kt | 2 +- .../test/kotlin/guide/example-compose-02.kt | 2 +- .../test/kotlin/guide/example-compose-03.kt | 2 +- .../test/kotlin/guide/example-compose-04.kt | 2 +- .../test/kotlin/guide/example-context-01.kt | 1 + .../test/kotlin/guide/example-context-02.kt | 1 + .../test/kotlin/guide/example-context-03.kt | 1 + .../test/kotlin/guide/example-context-05.kt | 1 + .../test/kotlin/guide/example-context-06.kt | 1 + .../test/kotlin/guide/example-context-07.kt | 1 + .../test/kotlin/guide/example-context-08.kt | 1 + .../test/kotlin/guide/example-context-10.kt | 1 + .../test/kotlin/guide/example-select-01.kt | 3 +- .../test/kotlin/guide/example-select-02.kt | 1 + .../test/kotlin/guide/example-select-03.kt | 2 +- .../test/kotlin/guide/example-select-05.kt | 1 + .../src/test/kotlin/guide/example-sync-01.kt | 4 +- .../src/test/kotlin/guide/example-sync-01b.kt | 4 +- .../src/test/kotlin/guide/example-sync-02.kt | 4 +- .../src/test/kotlin/guide/example-sync-03.kt | 6 +- .../src/test/kotlin/guide/example-sync-04.kt | 4 +- .../src/test/kotlin/guide/example-sync-05.kt | 4 +- .../src/test/kotlin/guide/example-sync-06.kt | 7 +- .../src/test/kotlin/guide/example-sync-07.kt | 4 +- .../coroutines/experimental/AsyncTest.kt | 1 + .../experimental/AtomicCancellationTest.kt | 7 +- .../coroutines/experimental/CoroutinesTest.kt | 1 + .../experimental/channels/ActorLazyTest.kt | 7 +- .../experimental/channels/ActorTest.kt | 12 +-- .../channels/ArrayBroadcastChannelTest.kt | 6 +- .../experimental/channels/ArrayChannelTest.kt | 3 +- .../BroadcastChannelMultiReceiveStressTest.kt | 14 +-- .../channels/BroadcastChannelSubStressTest.kt | 11 +- .../channels/ChannelSendReceiveStressTest.kt | 15 ++- ...nflatedBroadcastChannelNotifyStressTest.kt | 9 +- .../channels/ConflatedBroadcastChannelTest.kt | 9 +- .../ConflatedChannelCloseStressTest.kt | 1 + .../channels/ConflatedChannelTest.kt | 13 +-- .../channels/ProduceConsumeTest.kt | 1 + .../experimental/channels/ProduceTest.kt | 7 +- .../channels/RendezvousChannelTest.kt | 6 +- .../channels/SimpleSendReceiveTest.kt | 16 ++- .../selects/SelectArrayChannelTest.kt | 15 ++- .../experimental/selects/SelectBiasTest.kt | 9 +- .../selects/SelectDeferredTest.kt | 5 +- .../experimental/selects/SelectJobTest.kt | 5 +- .../experimental/selects/SelectMutexTest.kt | 13 +-- .../selects/SelectPhilosophersStressTest.kt | 7 +- .../selects/SelectRendezvousChannelTest.kt | 15 ++- .../coroutines/experimental/sync/MutexTest.kt | 5 +- .../io/ByteBufferChannelScenarioTest.kt | 10 +- .../experimental/io/ByteBufferChannelTest.kt | 33 +++--- .../io/CopyAndCloseNoAutoFlushTest.kt | 15 ++- .../experimental/io/CopyAndCloseTest.kt | 10 +- .../io/InlineRendezvousSwapTest.kt | 1 + .../coroutines/experimental/io/JavaIOTest.kt | 1 + .../experimental/io/ReadUntilDelimiterTest.kt | 13 +-- .../experimental/io/StringScenarioTest.kt | 15 ++- .../coroutines/experimental/io/StringsTest.kt | 12 +-- coroutines-guide.md | 102 ++++++++++++++---- .../guava/ListenableFutureTest.kt | 15 ++- .../coroutines/experimental/future/Future.kt | 3 +- .../experimental/future/FutureTest.kt | 13 ++- .../src/test/kotlin/examples/echo-example.kt | 16 ++- .../experimental/nio/AsyncIOTest.kt | 21 ++-- .../experimental/quasar/QuasarTest.kt | 13 ++- .../coroutines/experimental/Builders.kt | 3 +- .../coroutines/experimental/CoroutineScope.kt | 44 -------- .../coroutines/experimental/Deferred.kt | 8 +- .../kotlinx/coroutines/experimental/Job.kt | 23 +++- .../coroutines/experimental/Promise.kt | 3 +- .../coroutines/experimental/Scheduled.kt | 6 +- .../coroutines/experimental/PromiseTest.kt | 3 +- reactive/coroutines-guide-reactive.md | 47 ++++---- .../experimental/reactive/Publish.kt | 4 +- .../experimental/reactive/IntegrationTest.kt | 16 ++- .../experimental/reactive/PublishTest.kt | 15 ++- .../reactive/PublisherBackpressureTest.kt | 10 +- .../PublisherSubscriptionSelectTest.kt | 1 + .../experimental/reactor/ConvertTest.kt | 21 ++-- .../experimental/reactor/FluxTest.kt | 11 +- .../experimental/reactor/MonoTest.kt | 19 ++-- .../experimental/rx1/RxObservable.kt | 4 +- .../experimental/rx1/CompletableTest.kt | 13 +-- .../experimental/rx1/ConvertTest.kt | 10 +- .../experimental/rx1/IntegrationTest.kt | 16 ++- .../rx1/ObservableBackpressureTest.kt | 9 +- .../rx1/ObservableSubscriptionSelectTest.kt | 1 + .../experimental/rx1/ObservableTest.kt | 11 +- .../coroutines/experimental/rx1/SingleTest.kt | 17 ++- .../experimental/rx2/RxObservable.kt | 4 +- .../kotlin/guide/example-reactive-basic-01.kt | 1 + .../kotlin/guide/example-reactive-basic-02.kt | 1 + .../kotlin/guide/example-reactive-basic-03.kt | 1 + .../kotlin/guide/example-reactive-basic-04.kt | 1 + .../kotlin/guide/example-reactive-basic-05.kt | 5 +- .../kotlin/guide/example-reactive-basic-08.kt | 9 +- .../kotlin/guide/example-reactive-basic-09.kt | 8 +- .../guide/example-reactive-operators-02.kt | 4 +- .../guide/example-reactive-operators-03.kt | 6 +- .../guide/example-reactive-operators-04.kt | 4 +- .../experimental/rx2/CompletableTest.kt | 12 +-- .../experimental/rx2/ConvertTest.kt | 8 +- .../experimental/rx2/FlowableTest.kt | 11 +- .../experimental/rx2/IntegrationTest.kt | 16 ++- .../coroutines/experimental/rx2/MaybeTest.kt | 21 ++-- .../rx2/ObservableSubscriptionSelectTest.kt | 1 + .../experimental/rx2/ObservableTest.kt | 11 +- .../coroutines/experimental/rx2/SingleTest.kt | 15 ++- 133 files changed, 559 insertions(+), 575 deletions(-) delete mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineScope.kt rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt (54%) delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt index b1f1877f3b..5143c15a39 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt @@ -47,6 +47,7 @@ public abstract class AbstractCoroutine( ) : JobSupport(active), Job, Continuation, CoroutineScope { @Suppress("LeakingThis") public final override val context: CoroutineContext = parentContext + this + @Deprecated("Replaced with context", replaceWith = ReplaceWith("context")) public final override val coroutineContext: CoroutineContext get() = context /** diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineScope.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineScope.kt deleted file mode 100644 index c523800c67..0000000000 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineScope.kt +++ /dev/null @@ -1,24 +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.CoroutineContext - -public expect interface CoroutineScope { - public val isActive: Boolean - public val coroutineContext: CoroutineContext -} \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonJob.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonJob.kt index c6d22d0c11..5fb9b847e4 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonJob.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonJob.kt @@ -48,6 +48,7 @@ public expect interface DisposableHandle { public fun dispose() } +public expect val CoroutineContext.isActive: Boolean @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") public expect fun CoroutineContext.cancel(cause: Throwable? = null): Boolean @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt similarity index 54% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt index 222919ec80..875574a409 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt @@ -16,11 +16,12 @@ package kotlinx.coroutines.experimental -import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.* +import kotlin.internal.* /** - * Receiver interface for generic coroutine builders, so that the code inside coroutine has a convenient access - * to its [coroutineContext] and its cancellation status via [isActive]. + * Receiver interface for generic coroutine builders, so that the code inside coroutine has a convenient + * and fast access to its own cancellation status via [isActive]. */ public interface CoroutineScope { /** @@ -33,20 +34,22 @@ public interface CoroutineScope { * } * ``` * - * This property is a shortcut for `coroutineContext[Job]!!.isActive`. See [coroutineContext] and [Job]. + * This property is a shortcut for `coroutineContext.isActive` in the scope when + * [CoroutineScope] is available. + * See [coroutineContext][kotlin.coroutines.experimental.coroutineContext], + * [isActive][kotlinx.coroutines.experimental.isActive] and [Job.isActive]. */ - public actual val isActive: Boolean - - /** - * Returns the context of this coroutine. - * @suppress **Deprecated**: Renamed to [coroutineContext] - */ - @Deprecated("Renamed to `coroutineContext`", replaceWith = ReplaceWith("coroutineContext")) - public val context: CoroutineContext + public val isActive: Boolean /** * Returns the context of this coroutine. + * + * @suppress: **Deprecated**: Replaced with top-level [kotlin.coroutines.experimental.coroutineContext]. */ - @Suppress("DEPRECATION", "ACTUAL_WITHOUT_EXPECT") - public actual val coroutineContext: CoroutineContext get() = context + @Deprecated("Replace with top-level coroutineContext", + replaceWith = ReplaceWith("coroutineContext", + imports = ["kotlin.coroutines.experimental.coroutineContext"])) + @LowPriorityInOverloadResolution + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + public val coroutineContext: CoroutineContext } \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AbstractCoroutineTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AbstractCoroutineTest.kt index b380354daf..bafb2eb9b5 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AbstractCoroutineTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AbstractCoroutineTest.kt @@ -16,12 +16,14 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class AbstractCoroutineTest : TestBase() { @Test fun testNotifications() = runTest { expect(1) + val coroutineContext = coroutineContext // workaround for KT-22984 val coroutine = object : AbstractCoroutine(coroutineContext, false) { override fun onStart() { expect(3) @@ -59,6 +61,7 @@ class AbstractCoroutineTest : TestBase() { @Test fun testNotificationsWithException() = runTest { expect(1) + val coroutineContext = coroutineContext // workaround for KT-22984 val coroutine = object : AbstractCoroutine(coroutineContext, false) { override fun onStart() { expect(3) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncLazyTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncLazyTest.kt index a56084d33e..32e100ecdf 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncLazyTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncLazyTest.kt @@ -18,6 +18,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonAsyncLazyTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncTest.kt index f933f72d44..28349bee08 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncTest.kt @@ -18,6 +18,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonAsyncTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAtomicCancellationTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAtomicCancellationTest.kt index b336bfdee0..e1008c6235 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAtomicCancellationTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAtomicCancellationTest.kt @@ -16,6 +16,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonAtomicCancellationTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferredTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferredTest.kt index 67556e5067..2e371d57a6 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferredTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferredTest.kt @@ -18,6 +18,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonCompletableDeferredTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandlerTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandlerTest.kt index fb0e31d094..4a6975362e 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandlerTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandlerTest.kt @@ -16,6 +16,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonCoroutineExceptionHandlerTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt index b94ca92abe..749475b6da 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt @@ -18,6 +18,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonCoroutinesTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonJobTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonJobTest.kt index 1b9e822533..3ff6f7e2e7 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonJobTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonJobTest.kt @@ -16,6 +16,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonJobTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonLaunchLazyTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonLaunchLazyTest.kt index 29651f563c..42f4fb87c4 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonLaunchLazyTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonLaunchLazyTest.kt @@ -16,6 +16,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonLaunchLazyTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithContextTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithContextTest.kt index 7a3f4e9c0d..dde48b5f7f 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithContextTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithContextTest.kt @@ -19,9 +19,8 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* -import kotlin.coroutines.experimental.ContinuationInterceptor -import kotlin.coroutines.experimental.CoroutineContext class CommonWithContextTest : TestBase() { @Test diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutOrNullTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutOrNullTest.kt index 1a1ec91b4b..891c9ef1ef 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutOrNullTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutOrNullTest.kt @@ -19,6 +19,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonWithTimeoutOrNullTest : TestBase() { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutTest.kt index e5360dea6c..95894389c5 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutTest.kt @@ -19,6 +19,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CommonWithTimeoutTest : TestBase() { diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt index 497ba9073c..d54986c9c9 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt @@ -32,7 +32,8 @@ import kotlin.coroutines.experimental.intrinsics.* * * 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.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used, + * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt index 1edf519321..ddc8e0ece7 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt @@ -66,8 +66,9 @@ import kotlin.coroutines.experimental.* * +-----------+ * ``` * - * A deferred value is a [Job]. A job in the coroutine [context][CoroutineScope.coroutineContext] of [async] builder - * represents the coroutine itself. + * A deferred value is a [Job]. A job in the + * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * of [async] builder represents the coroutine itself. * A deferred value is active while the coroutine is working and cancellation aborts the coroutine when * the coroutine is suspended on a _cancellable_ suspension point by throwing [CancellationException] * or the cancellation cause inside the coroutine. @@ -143,7 +144,8 @@ public actual interface Deferred : Job { * * 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.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used, + * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 index 8180cf28f7..6c03c077cd 100644 --- 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 @@ -73,7 +73,9 @@ import kotlin.coroutines.experimental.intrinsics.* * +-----------+ * ``` * - * A job in the [coroutineContext][CoroutineScope.coroutineContext] represents the coroutine itself. + * A job in the + * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * 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]. * @@ -463,6 +465,25 @@ public actual suspend fun Job.joinChildren() { // -------------------- CoroutineContext extensions -------------------- +/** + * Returns `true` when the [Job] of the coroutine in this context is still active + * (has not completed and was not cancelled yet). + * + * Check this property in long-running computation loops to support cancellation + * when [CoroutineScope.isActive] is not available: + * + * ``` + * while (coroutineContext.isActive) { + * // do some computation + * } + * ``` + * + * The `coroutineContext.isActive` expression is a shortcut for `coroutineContext[Job]?.isActive == true`. + * See [Job.isActive]. + */ +public actual val CoroutineContext.isActive: Boolean + get() = this[Job]?.isActive == true + /** * 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 diff --git a/core/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 index 603308f2e9..8c6b208a57 100644 --- a/core/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 @@ -203,7 +203,7 @@ public interface SelectInstance { * Note, that this function does not check for cancellation when it is not suspended. * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed. */ -public inline suspend fun select(crossinline builder: SelectBuilder.() -> Unit): R = +public suspend inline fun select(crossinline builder: SelectBuilder.() -> Unit): R = suspendCoroutineOrReturn { cont -> val scope = SelectBuilderImpl(cont) try { diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt index b14666ec12..166a4addf5 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-05.kt @@ -19,7 +19,7 @@ package guide.channel.example05 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* -import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.* fun numbersFrom(context: CoroutineContext, start: Int) = produce(context) { var x = start diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt index d10c5ed82c..ee7493036a 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-07.kt @@ -19,6 +19,7 @@ package guide.channel.example07 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* +import kotlin.coroutines.experimental.* suspend fun sendString(channel: SendChannel, s: String, time: Long) { while (true) { diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt index 096f435edd..484cd88c94 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-08.kt @@ -19,6 +19,7 @@ package guide.channel.example08 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { val channel = Channel(4) // create buffered channel diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt index 2a5e064004..fe93386f83 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-channel-09.kt @@ -19,6 +19,7 @@ package guide.channel.example09 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* +import kotlin.coroutines.experimental.* data class Ball(var hits: Int) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt index af083f30f2..d4a09013db 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-01.kt @@ -18,7 +18,7 @@ package guide.compose.example01 import kotlinx.coroutines.experimental.* -import kotlin.system.measureTimeMillis +import kotlin.system.* suspend fun doSomethingUsefulOne(): Int { delay(1000L) // pretend we are doing something useful here diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt index f39d98e419..b6dfd4adf3 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-02.kt @@ -18,7 +18,7 @@ package guide.compose.example02 import kotlinx.coroutines.experimental.* -import kotlin.system.measureTimeMillis +import kotlin.system.* suspend fun doSomethingUsefulOne(): Int { delay(1000L) // pretend we are doing something useful here diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt index adc8eabd4c..d3745fe4a9 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-03.kt @@ -18,7 +18,7 @@ package guide.compose.example03 import kotlinx.coroutines.experimental.* -import kotlin.system.measureTimeMillis +import kotlin.system.* suspend fun doSomethingUsefulOne(): Int { delay(1000L) // pretend we are doing something useful here diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt index 4161bdd959..f85002ce27 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt @@ -18,7 +18,7 @@ package guide.compose.example04 import kotlinx.coroutines.experimental.* -import kotlin.system.measureTimeMillis +import kotlin.system.* suspend fun doSomethingUsefulOne(): Int { delay(1000L) // pretend we are doing something useful here diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt index cfe3a48a9d..9472d067af 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-01.kt @@ -18,6 +18,7 @@ package guide.context.example01 import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { val jobs = arrayListOf() diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt index 286860de0c..a62929f293 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-02.kt @@ -18,6 +18,7 @@ package guide.context.example02 import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { val jobs = arrayListOf() diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt index ce622885df..86b3004aba 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-03.kt @@ -18,6 +18,7 @@ package guide.context.example03 import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun log(msg: String) = println("[${Thread.currentThread().name}] $msg") diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt index b58185e631..8eb44ff37f 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-05.kt @@ -18,6 +18,7 @@ package guide.context.example05 import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { println("My job is ${coroutineContext[Job]}") diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt index b69a2afdeb..ecb012c1a8 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-06.kt @@ -18,6 +18,7 @@ package guide.context.example06 import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { // launch a coroutine to process some kind of incoming request diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt index 0ad533af32..1093685c2a 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-07.kt @@ -18,6 +18,7 @@ package guide.context.example07 import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { // start a coroutine to process some kind of incoming request 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 index a5675efea6..7f2fc2bbae 100644 --- 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 @@ -18,6 +18,7 @@ package guide.context.example08 import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { // launch a coroutine to process some kind of incoming request diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt index ab26685c24..ad98c11b54 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt @@ -18,6 +18,7 @@ package guide.context.example10 import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { val job = Job() // create a job object to manage our lifecycle diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt index 9e46a7c5e9..9a07954eb8 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-01.kt @@ -20,7 +20,8 @@ package guide.select.example01 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.selects.* -import kotlin.coroutines.experimental.CoroutineContext +import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun fizz(context: CoroutineContext) = produce(context) { while (true) { // sends "Fizz" every 300 ms diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt index b128877aad..ee8421b2bd 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-02.kt @@ -20,6 +20,7 @@ package guide.select.example02 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.selects.* +import kotlin.coroutines.experimental.* suspend fun selectAorB(a: ReceiveChannel, b: ReceiveChannel): String = select { diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-03.kt index acff08fe01..4674e9fe7e 100644 --- a/core/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,7 +20,7 @@ package guide.select.example03 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.selects.* -import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.* fun produceNumbers(context: CoroutineContext, side: SendChannel) = produce(context) { for (num in 1..10) { // produce 10 numbers from 1 to 10 diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt index 84ba4e5b53..ba02006b1e 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt @@ -20,6 +20,7 @@ package guide.select.example05 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.selects.* +import kotlin.coroutines.experimental.* fun switchMapDeferreds(input: ReceiveChannel>) = produce { var current = input.receive() // start with first received deferred value diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt index 6d2de04b03..b761c7d208 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01.kt @@ -18,8 +18,8 @@ package guide.sync.example01 import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.system.measureTimeMillis +import kotlin.system.* +import kotlin.coroutines.experimental.* suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt index 68b320d430..c032747ca4 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-01b.kt @@ -18,8 +18,8 @@ package guide.sync.example01b import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.system.measureTimeMillis +import kotlin.system.* +import kotlin.coroutines.experimental.* suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt index ecba342d19..e5cf1251c3 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-02.kt @@ -18,8 +18,8 @@ package guide.sync.example02 import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.system.measureTimeMillis +import kotlin.system.* +import kotlin.coroutines.experimental.* suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt index bc1dfaf5e2..37dd5d8bac 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-03.kt @@ -18,9 +18,9 @@ package guide.sync.example03 import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.system.measureTimeMillis -import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.* +import kotlin.system.* +import kotlin.coroutines.experimental.* suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt index 1adff1446c..bd69d1218d 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-04.kt @@ -18,8 +18,8 @@ package guide.sync.example04 import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.system.measureTimeMillis +import kotlin.system.* +import kotlin.coroutines.experimental.* suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt index adf8612aeb..cfd5c19554 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-05.kt @@ -18,8 +18,8 @@ package guide.sync.example05 import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.system.measureTimeMillis +import kotlin.system.* +import kotlin.coroutines.experimental.* suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt index 73fe1163a0..73702dd73d 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-06.kt @@ -18,10 +18,9 @@ package guide.sync.example06 import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.system.measureTimeMillis -import kotlinx.coroutines.experimental.sync.Mutex -import kotlinx.coroutines.experimental.sync.withLock +import kotlinx.coroutines.experimental.sync.* +import kotlin.system.* +import kotlin.coroutines.experimental.* suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt index 433d6886c7..4d00d7c85a 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/guide/example-sync-07.kt @@ -18,9 +18,9 @@ package guide.sync.example07 import kotlinx.coroutines.experimental.* -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.system.measureTimeMillis import kotlinx.coroutines.experimental.channels.* +import kotlin.system.* +import kotlin.coroutines.experimental.* suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt index adf04506fc..7b93917505 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt @@ -16,6 +16,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class AsyncTest : TestBase() { diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt index 6d43cc16b5..9d7a20a839 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt @@ -16,9 +16,10 @@ package kotlinx.coroutines.experimental -import kotlinx.coroutines.experimental.channels.Channel -import kotlinx.coroutines.experimental.selects.select -import kotlinx.coroutines.experimental.sync.Mutex +import kotlinx.coroutines.experimental.channels.* +import kotlinx.coroutines.experimental.selects.* +import kotlinx.coroutines.experimental.sync.* +import kotlin.coroutines.experimental.* import kotlin.test.* class AtomicCancellationTest : TestBase() { diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt index 4c284b033a..58e7bfeaa2 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt @@ -16,6 +16,7 @@ package kotlinx.coroutines.experimental +import kotlin.coroutines.experimental.* import kotlin.test.* class CoroutinesTest : TestBase() { diff --git a/core/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 index b0a2ac9a5b..4a06251772 100644 --- a/core/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 @@ -17,9 +17,10 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual -import org.junit.Assert.assertThat -import org.junit.Test +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class ActorLazyTest : TestBase() { @Test diff --git a/core/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 index c757d2963c..1494d2f653 100644 --- a/core/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 @@ -16,13 +16,11 @@ package kotlinx.coroutines.experimental.channels -import kotlinx.coroutines.experimental.Job -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.junit.Assert.assertThat -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class ActorTest : TestBase() { @Test diff --git a/core/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 index e5c4609d7a..a71e3f8a56 100644 --- a/core/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 @@ -17,10 +17,10 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsNull +import org.hamcrest.core.* +import org.junit.* import org.junit.Assert.* -import org.junit.Test +import kotlin.coroutines.experimental.* class ArrayBroadcastChannelTest : TestBase() { @Test diff --git a/core/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 index 8e71093887..80d3682127 100644 --- a/core/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 @@ -17,8 +17,9 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* +import org.junit.* import org.junit.Assert.* -import org.junit.Test +import kotlin.coroutines.experimental.* class ArrayChannelTest : TestBase() { @Test diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt index cabb7620a2..7f341c1655 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt @@ -17,13 +17,13 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.selects.select -import org.junit.After -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicLong +import kotlinx.coroutines.experimental.selects.* +import org.junit.* +import org.junit.runner.* +import org.junit.runners.* +import java.util.concurrent.* +import java.util.concurrent.atomic.* +import kotlin.coroutines.experimental.* /** * Tests delivery of events to multiple broadcast channel subscribers. diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelSubStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelSubStressTest.kt index 8bd7b63b1c..21b238cb9d 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelSubStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelSubStressTest.kt @@ -17,11 +17,12 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicLong +import org.junit.* +import org.junit.runner.* +import org.junit.runners.* +import java.util.concurrent.* +import java.util.concurrent.atomic.* +import kotlin.coroutines.experimental.* /** * Creates a broadcast channel and repeatedly opens new subscription, receives event, closes it, diff --git a/core/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 index 98fc716470..4a98f40668 100644 --- a/core/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 @@ -17,14 +17,13 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.selects.select -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicIntegerArray +import kotlinx.coroutines.experimental.selects.* +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.* +import org.junit.runners.* +import java.util.concurrent.atomic.* +import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) class ChannelSendReceiveStressTest( diff --git a/core/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 index bc5324e7ed..64df05c05c 100644 --- a/core/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 @@ -17,10 +17,11 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.core.IsEqual -import org.junit.Test -import java.util.concurrent.atomic.AtomicInteger +import org.hamcrest.MatcherAssert.* +import org.hamcrest.core.* +import org.junit.* +import java.util.concurrent.atomic.* +import kotlin.coroutines.experimental.* class ConflatedBroadcastChannelNotifyStressTest : TestBase() { val nSenders = 2 diff --git a/core/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 index b3d197b06d..707b780d6e 100644 --- a/core/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 @@ -17,11 +17,10 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.hamcrest.core.IsNull -import org.junit.Assert.assertThat -import org.junit.Test +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class ConflatedBroadcastChannelTest : TestBase() { @Test diff --git a/core/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 index dfa6f8def8..201258c1f8 100644 --- a/core/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 @@ -21,6 +21,7 @@ import org.junit.After import org.junit.Test import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference +import kotlin.coroutines.experimental.* class ConflatedChannelCloseStressTest : TestBase() { diff --git a/core/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 index 2db3537842..6819170f0e 100644 --- a/core/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 @@ -16,14 +16,11 @@ package kotlinx.coroutines.experimental.channels -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsNull -import org.junit.Assert.assertThat -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class ConflatedChannelTest : TestBase() { @Test diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt index 019e58a1ca..8aea78f7a6 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt @@ -21,6 +21,7 @@ import org.junit.* import org.junit.Assert.* import org.junit.runner.* import org.junit.runners.* +import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) class ProduceConsumeTest( 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 index 167cbd74b2..641eff0f62 100644 --- 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 @@ -16,10 +16,9 @@ package kotlinx.coroutines.experimental.channels -import kotlinx.coroutines.experimental.Job -import kotlinx.coroutines.experimental.JobCancellationException -import kotlinx.coroutines.experimental.TestBase -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.junit.* +import kotlin.coroutines.experimental.* class ProduceTest : TestBase() { @Test diff --git a/core/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 index e77ba123d5..7edbe42bf6 100644 --- a/core/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 @@ -17,10 +17,10 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsNull +import org.hamcrest.core.* +import org.junit.* import org.junit.Assert.* -import org.junit.Test +import kotlin.coroutines.experimental.* class RendezvousChannelTest : TestBase() { @Test diff --git a/core/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 index b36aa53433..feb06e0438 100644 --- a/core/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 @@ -16,15 +16,13 @@ package kotlinx.coroutines.experimental.channels -import kotlinx.coroutines.experimental.CommonPool -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import org.hamcrest.core.IsEqual -import org.junit.Assert.assertThat -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.* +import org.junit.runners.* +import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) class SimpleSendReceiveTest( diff --git a/core/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 index 0620ed185b..54aaae0ca5 100644 --- a/core/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 @@ -16,15 +16,12 @@ package kotlinx.coroutines.experimental.selects -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.channels.ArrayChannel -import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException -import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.junit.Assert.assertEquals -import org.junit.Test +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.channels.* +import kotlinx.coroutines.experimental.intrinsics.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class SelectArrayChannelTest : TestBase() { @Test diff --git a/core/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 index 6f5682d4bc..eee7283897 100644 --- a/core/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 @@ -16,11 +16,10 @@ package kotlinx.coroutines.experimental.selects -import kotlinx.coroutines.experimental.async -import kotlinx.coroutines.experimental.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class SelectBiasTest { val n = 10_000 diff --git a/core/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 index cbf1e6c50b..3ab8955086 100644 --- a/core/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 @@ -17,8 +17,9 @@ package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* -import org.junit.Assert.assertEquals -import org.junit.Test +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class SelectDeferredTest : TestBase() { @Test diff --git a/core/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 index 9d38aaf531..16cffda545 100644 --- a/core/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 @@ -17,8 +17,9 @@ package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* -import org.junit.Assert.assertEquals -import org.junit.Test +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class SelectJobTest : TestBase() { @Test diff --git a/core/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 index 4f60faf49e..96c94a4ac1 100644 --- a/core/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 @@ -16,14 +16,11 @@ package kotlinx.coroutines.experimental.selects -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.sync.Mutex -import kotlinx.coroutines.experimental.sync.MutexImpl -import kotlinx.coroutines.experimental.yield -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.sync.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class SelectMutexTest : TestBase() { @Test diff --git a/core/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 index 05892d6f91..4528a4c085 100644 --- a/core/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 @@ -17,9 +17,10 @@ package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.sync.Mutex -import org.junit.Assert.assertTrue -import org.junit.Test +import kotlinx.coroutines.experimental.sync.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class SelectPhilosophersStressTest : TestBase() { val TEST_DURATION = 3000L * stressTestMultiplier diff --git a/core/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 index b377dd6d9f..d71be6ae2a 100644 --- a/core/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 @@ -16,15 +16,12 @@ package kotlinx.coroutines.experimental.selects -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException -import kotlinx.coroutines.experimental.channels.RendezvousChannel -import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.junit.Assert.assertEquals -import org.junit.Test +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.channels.* +import kotlinx.coroutines.experimental.intrinsics.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class SelectRendezvousChannelTest : TestBase() { @Test diff --git a/core/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 index 4a3fd32290..1d3fd30cd6 100644 --- a/core/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 @@ -17,9 +17,10 @@ package kotlinx.coroutines.experimental.sync import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual +import org.hamcrest.core.* +import org.junit.* import org.junit.Assert.* -import org.junit.Test +import kotlin.coroutines.experimental.* class MutexTest : TestBase() { @Test 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 index 5a31352d3d..c7afd0e29c 100644 --- 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 @@ -1,14 +1,12 @@ package kotlinx.coroutines.experimental.io -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.io.internal.* -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield import org.junit.* import org.junit.Test -import java.io.IOException +import java.io.* +import kotlin.coroutines.experimental.* import kotlin.test.* class ByteBufferChannelScenarioTest : TestBase() { 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 index f21fa008c3..091e29c1c6 100644 --- 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 @@ -1,29 +1,22 @@ package kotlinx.coroutines.experimental.io import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException -import kotlinx.coroutines.experimental.io.internal.BUFFER_SIZE -import kotlinx.coroutines.experimental.io.internal.RESERVED_SIZE -import kotlinx.coroutines.experimental.io.internal.ReadWriteBufferState +import kotlinx.coroutines.experimental.CancellationException +import kotlinx.coroutines.experimental.channels.* +import kotlinx.coroutines.experimental.io.internal.* +import kotlinx.coroutines.experimental.io.packet.* import kotlinx.coroutines.experimental.io.packet.ByteReadPacket -import kotlinx.coroutines.experimental.io.packet.ByteWritePacket -import kotlinx.io.core.BufferView -import kotlinx.io.core.BytePacketBuilder -import kotlinx.io.core.readUTF8Line -import kotlinx.io.pool.DefaultPool -import kotlinx.io.pool.NoPoolImpl +import kotlinx.io.core.* +import kotlinx.io.pool.* import org.junit.* -import org.junit.rules.ErrorCollector -import org.junit.rules.Timeout +import org.junit.Test +import org.junit.rules.* import java.nio.CharBuffer import java.util.* -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue -import kotlin.test.fail +import java.util.concurrent.* +import java.util.concurrent.atomic.* +import kotlin.coroutines.experimental.* +import kotlin.test.* class ByteBufferChannelTest : TestBase() { @get:Rule @@ -822,7 +815,7 @@ class ByteBufferChannelTest : TestBase() { latch.await() } - private fun CoroutineScope.launch(name: String = "child", block: suspend () -> Unit): Job { + private suspend fun launch(name: String = "child", block: suspend () -> Unit): Job { return launch(context = DefaultDispatcher + CoroutineName(name), parent = coroutineContext[Job]) { block() }.apply { diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/CopyAndCloseNoAutoFlushTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/CopyAndCloseNoAutoFlushTest.kt index f026567ba0..ab02c296f3 100644 --- a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/CopyAndCloseNoAutoFlushTest.kt +++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/CopyAndCloseNoAutoFlushTest.kt @@ -1,15 +1,12 @@ package kotlinx.coroutines.experimental.io -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.io.internal.BufferObjectPool -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.junit.Rule +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.io.internal.* +import org.junit.* import org.junit.Test -import org.junit.rules.TestRule -import kotlin.test.assertEquals -import kotlin.test.assertTrue +import org.junit.rules.* +import kotlin.coroutines.experimental.* +import kotlin.test.* class CopyAndCloseNoAutoFlushTest : TestBase() { private val verifyingPool = VerifyingObjectPool(BufferObjectPool) 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 index edd0b1486b..504a10352b 100644 --- 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 @@ -1,12 +1,12 @@ package kotlinx.coroutines.experimental.io import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.io.internal.asByteBuffer -import org.junit.After +import kotlinx.coroutines.experimental.io.internal.* +import org.junit.* import org.junit.Test -import java.io.IOException -import kotlin.test.assertEquals -import kotlin.test.fail +import java.io.* +import kotlin.coroutines.experimental.* +import kotlin.test.* class CopyAndCloseTest : TestBase() { private val from = ByteChannel(true) diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/InlineRendezvousSwapTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/InlineRendezvousSwapTest.kt index 549f7039a1..8d868c266a 100644 --- a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/InlineRendezvousSwapTest.kt +++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/InlineRendezvousSwapTest.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.io.internal.* import org.junit.Ignore import org.junit.Test +import kotlin.coroutines.experimental.* import kotlin.test.* @Ignore diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/JavaIOTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/JavaIOTest.kt index 91a7ad0e3f..c8bc8e729d 100644 --- a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/JavaIOTest.kt +++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/JavaIOTest.kt @@ -7,6 +7,7 @@ import org.junit.Test import java.io.* import java.nio.channels.* import java.util.* +import kotlin.coroutines.experimental.* import kotlin.test.* class JavaIOTest : TestBase() { 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 index e18e40623b..5001037b71 100644 --- 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 @@ -1,15 +1,12 @@ package kotlinx.coroutines.experimental.io import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.io.internal.BUFFER_SIZE -import kotlinx.coroutines.experimental.io.internal.asByteBuffer -import org.junit.After -import org.junit.Before +import kotlinx.coroutines.experimental.io.internal.* +import org.junit.* import org.junit.Test -import java.io.IOException -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlin.test.fail +import java.io.* +import kotlin.coroutines.experimental.* +import kotlin.test.* class ReadUntilDelimiterTest : TestBase() { private val source = ByteChannel(true) 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 index 3ccd35e81b..3f492339d2 100644 --- 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 @@ -1,15 +1,12 @@ 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 kotlinx.coroutines.experimental.* +import org.junit.* import org.junit.Test -import org.junit.rules.Timeout -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertTrue +import org.junit.rules.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* +import kotlin.test.* class StringScenarioTest : TestBase() { @get:Rule 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 index 0e2d32a581..a1a81f9ee4 100644 --- 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 @@ -1,15 +1,13 @@ package kotlinx.coroutines.experimental.io import kotlinx.coroutines.experimental.* -import org.junit.Rule +import org.junit.* import org.junit.Test -import org.junit.rules.Timeout +import org.junit.rules.* import java.util.* -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.assertNull -import kotlin.test.fail +import java.util.concurrent.* +import kotlin.coroutines.experimental.* +import kotlin.test.* class StringsTest : TestBase() { @get:Rule diff --git a/coroutines-guide.md b/coroutines-guide.md index 59b86b81eb..65e9130b97 100644 --- a/coroutines-guide.md +++ b/coroutines-guide.md @@ -620,7 +620,7 @@ remote service call or computation. We just pretend they are useful, but actuall delays for a second for the purpose of this example: ```kotlin @@ -807,6 +807,10 @@ parameter that can be used to explicitly specify the dispatcher for new coroutin Try the following example: + + ```kotlin fun main(args: Array) = runBlocking { val jobs = arrayListOf() @@ -843,7 +847,8 @@ The default dispatcher that we've used in previous sections is representend by [ is equal to [CommonPool] in the current implementation. So, `launch { ... }` is the same as `launch(DefaultDispatcher) { ... }`, which is the same as `launch(CommonPool) { ... }`. -The difference between parent [coroutineContext][CoroutineScope.coroutineContext] and +The difference between parent +[coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) and [Unconfined] context will be shown later. Note, that [newSingleThreadContext] creates a new thread, which is a very expensive resource. @@ -857,12 +862,17 @@ first suspension point. After suspension it resumes in the thread that is fully suspending function that was invoked. Unconfined dispatcher is appropriate when coroutine does not consume CPU time nor updates any shared data (like UI) that is confined to a specific thread. -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. +On the other side, +[coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) +property, that is available inside any coroutine, is a reference to a context of this particular coroutine. 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 fun main(args: Array) = runBlocking { val jobs = arrayListOf() @@ -908,6 +918,10 @@ by logging frameworks. When using coroutines, the thread name alone does not giv Run the following code with `-Dkotlinx.coroutines.debug` JVM option: + + ```kotlin fun log(msg: String) = println("[${Thread.currentThread().name}] $msg") @@ -990,6 +1004,10 @@ are created with [newSingleThreadContext] when they are no longer needed. The coroutine's [Job] is part of its context. The coroutine can retrieve it from its own context using `coroutineContext[Job]` expression: + + ```kotlin fun main(args: Array) = runBlocking { println("My job is ${coroutineContext[Job]}") @@ -1007,15 +1025,21 @@ My job is "coroutine#1":BlockingCoroutine{Active}@6d311334 So, [isActive][CoroutineScope.isActive] in [CoroutineScope] is just a convenient shortcut for -`coroutineContext[Job]!!.isActive`. +`coroutineContext[Job]?.isActive == true`. ### Children of a coroutine -When [coroutineContext][CoroutineScope.coroutineContext] of a coroutine is used to launch another coroutine, +When +[coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) +of a coroutine is used to launch another coroutine, the [Job] of the new coroutine becomes a _child_ of the parent coroutine's job. When the parent coroutine is cancelled, all its children are recursively cancelled, too. + + ```kotlin fun main(args: Array) = runBlocking { // launch a coroutine to process some kind of incoming request @@ -1063,6 +1087,10 @@ Coroutine contexts can be combined using `+` operator. The context on the right- of the context on the left-hand side. For example, a [Job] of the parent coroutine can be inherited, while its dispatcher replaced: + + ```kotlin fun main(args: Array) = runBlocking { // start a coroutine to process some kind of incoming request @@ -1098,6 +1126,10 @@ main: Who has survived request cancellation? 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) = runBlocking { // launch a coroutine to process some kind of incoming request @@ -1189,6 +1221,10 @@ Now, a single invocation of [Job.cancel] cancels all the children we've launched 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) = runBlocking { val job = Job() // create a job object to manage our lifecycle @@ -1394,8 +1430,8 @@ of coroutines. We start with an infinite sequence of numbers. This time we intro explicit `context` parameter and pass it to [produce] builder, so that caller can control where our coroutines run: - ```kotlin @@ -1423,8 +1459,9 @@ numbersFrom(2) -> filter(2) -> filter(3) -> filter(5) -> filter(7) ... The following example prints the first ten prime numbers, running the whole pipeline in the context of the main thread. Since all the coroutines are launched 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. +children of the main [runBlocking] coroutine in its +[coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html), +we don't have to keep an explicit list of all the coroutines we have started. We use [cancelChildren][kotlin.coroutines.experimental.CoroutineContext.cancelChildren] extension function to cancel all the children coroutines. @@ -1539,6 +1576,10 @@ Multiple coroutines may send to the same channel. For example, let us have a channel of strings, and a suspending function that repeatedly sends a specified string to this channel with a specified delay: + + ```kotlin suspend fun sendString(channel: SendChannel, s: String, time: Long) { while (true) { @@ -1590,6 +1631,10 @@ similar to the `BlockingQueue` with a specified capacity, which blocks when buff Take a look at the behavior of the following code: + + ```kotlin fun main(args: Array) = runBlocking { val channel = Channel(4) // create buffered channel @@ -1628,6 +1673,10 @@ multiple coroutines. They are served in first-in first-out order, e.g. the first gets the element. In the following example two coroutines "ping" and "pong" are receiving the "ball" object from the shared "table" channel. + + ```kotlin data class Ball(var hits: Int) @@ -1680,24 +1729,23 @@ but others are unique. Let us launch a thousand coroutines all doing the same action thousand times (for a total of a million executions). We'll also measure their completion time for further comparisons: - - + + ```kotlin suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) { val n = 1000 // number of coroutines to launch @@ -1999,8 +2047,9 @@ import kotlinx.coroutines.experimental.selects.* Let us have two producers of strings: `fizz` and `buzz`. The `fizz` produces "Fizz" string every 300 ms: - ```kotlin @@ -2076,6 +2125,10 @@ The [onReceive][ReceiveChannel.onReceive] clause in `select` fails when the chan specific action when the channel is closed. The following example also shows that `select` is an expression that returns the result of its selected clause: + + ```kotlin suspend fun selectAorB(a: ReceiveChannel, b: ReceiveChannel): String = select { @@ -2149,7 +2202,7 @@ Let us write an example of producer of integers that sends its values to a `side the consumers on its primary channel cannot keep up with it: ```kotlin @@ -2165,7 +2218,7 @@ fun produceNumbers(context: CoroutineContext, side: SendChannel) = produce< ``` Consumer is going to be quite slow, taking 250 ms to process each number: - + ```kotlin fun main(args: Array) = runBlocking { val side = Channel() // allocate side channel @@ -2265,6 +2318,10 @@ Let us write a channel producer function that consumes a channel of deferred str deferred value, but only until the next deferred value comes over or the channel is closed. This example puts together [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`: + + ```kotlin fun switchMapDeferreds(input: ReceiveChannel>) = produce { var current = input.receive() // start with first received deferred value @@ -2365,7 +2422,6 @@ Channel was closed [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 -[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 [newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-single-thread-context.html [ThreadPoolDispatcher.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-thread-pool-dispatcher/close.html diff --git a/integration/kotlinx-coroutines-guava/src/test/kotlin/kotlinx/coroutines/experimental/guava/ListenableFutureTest.kt b/integration/kotlinx-coroutines-guava/src/test/kotlin/kotlinx/coroutines/experimental/guava/ListenableFutureTest.kt index 767bb1cbf5..7db81cac2d 100644 --- a/integration/kotlinx-coroutines-guava/src/test/kotlin/kotlinx/coroutines/experimental/guava/ListenableFutureTest.kt +++ b/integration/kotlinx-coroutines-guava/src/test/kotlin/kotlinx/coroutines/experimental/guava/ListenableFutureTest.kt @@ -16,17 +16,14 @@ package kotlinx.coroutines.experimental.guava -import com.google.common.util.concurrent.MoreExecutors -import com.google.common.util.concurrent.SettableFuture +import com.google.common.util.concurrent.* import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf +import kotlinx.coroutines.experimental.CancellationException +import org.hamcrest.core.* +import org.junit.* import org.junit.Assert.* -import org.junit.Before -import org.junit.Test -import java.util.concurrent.Callable -import java.util.concurrent.ExecutionException -import java.util.concurrent.ForkJoinPool +import java.util.concurrent.* +import kotlin.coroutines.experimental.* class ListenableFutureTest : TestBase() { @Before 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 e2d5117f54..ee908e3fc1 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 @@ -29,7 +29,8 @@ import kotlin.coroutines.experimental.* * * 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.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used, + * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * diff --git a/integration/kotlinx-coroutines-jdk8/src/test/kotlin/kotlinx/coroutines/experimental/future/FutureTest.kt b/integration/kotlinx-coroutines-jdk8/src/test/kotlin/kotlinx/coroutines/experimental/future/FutureTest.kt index 2996b212f7..136328723a 100644 --- a/integration/kotlinx-coroutines-jdk8/src/test/kotlin/kotlinx/coroutines/experimental/future/FutureTest.kt +++ b/integration/kotlinx-coroutines-jdk8/src/test/kotlin/kotlinx/coroutines/experimental/future/FutureTest.kt @@ -18,15 +18,14 @@ package kotlinx.coroutines.experimental.future import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.CancellationException -import org.hamcrest.core.IsEqual +import org.hamcrest.core.* +import org.junit.* import org.junit.Assert.* -import org.junit.Before -import org.junit.Test import java.util.concurrent.* -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock -import kotlin.coroutines.experimental.CoroutineContext +import java.util.concurrent.atomic.* +import java.util.concurrent.locks.* +import kotlin.concurrent.* +import kotlin.coroutines.experimental.* class FutureTest : TestBase() { @Before diff --git a/integration/kotlinx-coroutines-nio/src/test/kotlin/examples/echo-example.kt b/integration/kotlinx-coroutines-nio/src/test/kotlin/examples/echo-example.kt index e279cbd407..5692c35d69 100644 --- a/integration/kotlinx-coroutines-nio/src/test/kotlin/examples/echo-example.kt +++ b/integration/kotlinx-coroutines-nio/src/test/kotlin/examples/echo-example.kt @@ -16,16 +16,12 @@ package examples -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.nio.aAccept -import kotlinx.coroutines.experimental.nio.aRead -import kotlinx.coroutines.experimental.nio.aWrite -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.withTimeout -import java.net.InetSocketAddress -import java.nio.ByteBuffer -import java.nio.channels.AsynchronousServerSocketChannel -import java.nio.channels.AsynchronousSocketChannel +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.nio.* +import java.net.* +import java.nio.* +import java.nio.channels.* +import kotlin.coroutines.experimental.* val PORT = 12345 val CLIENT_READ_TIMEOUT = 5000L // 5 sec diff --git a/integration/kotlinx-coroutines-nio/src/test/kotlin/kotlinx/coroutines/experimental/nio/AsyncIOTest.kt b/integration/kotlinx-coroutines-nio/src/test/kotlin/kotlinx/coroutines/experimental/nio/AsyncIOTest.kt index 7fd38d8b1c..0121c51cbe 100644 --- a/integration/kotlinx-coroutines-nio/src/test/kotlin/kotlinx/coroutines/experimental/nio/AsyncIOTest.kt +++ b/integration/kotlinx-coroutines-nio/src/test/kotlin/kotlinx/coroutines/experimental/nio/AsyncIOTest.kt @@ -16,19 +16,16 @@ package kotlinx.coroutines.experimental.nio -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import org.apache.commons.io.FileUtils -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder -import java.net.InetSocketAddress -import java.nio.ByteBuffer -import java.nio.channels.AsynchronousFileChannel -import java.nio.channels.AsynchronousServerSocketChannel -import java.nio.channels.AsynchronousSocketChannel -import java.nio.file.StandardOpenOption +import kotlinx.coroutines.experimental.* +import org.apache.commons.io.* +import org.junit.* import org.junit.Assert.* +import org.junit.rules.* +import java.net.* +import java.nio.* +import java.nio.channels.* +import java.nio.file.* +import kotlin.coroutines.experimental.* class AsyncIOTest { @Rule diff --git a/integration/kotlinx-coroutines-quasar/src/test/kotlin/kotlinx/coroutines/experimental/quasar/QuasarTest.kt b/integration/kotlinx-coroutines-quasar/src/test/kotlin/kotlinx/coroutines/experimental/quasar/QuasarTest.kt index 0782882a6b..43f23e7e6c 100644 --- a/integration/kotlinx-coroutines-quasar/src/test/kotlin/kotlinx/coroutines/experimental/quasar/QuasarTest.kt +++ b/integration/kotlinx-coroutines-quasar/src/test/kotlin/kotlinx/coroutines/experimental/quasar/QuasarTest.kt @@ -16,14 +16,13 @@ package kotlinx.coroutines.experimental.quasar -import co.paralleluniverse.fibers.Fiber -import co.paralleluniverse.fibers.SuspendExecution -import co.paralleluniverse.strands.SuspendableCallable -import co.paralleluniverse.strands.dataflow.Val +import co.paralleluniverse.fibers.* +import co.paralleluniverse.strands.* +import co.paralleluniverse.strands.dataflow.* import kotlinx.coroutines.experimental.* -import org.junit.Before -import org.junit.Test -import java.util.concurrent.TimeUnit +import org.junit.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* class QuasarTest : TestBase() { @Before diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt index bf89cbe63e..896031ee67 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt @@ -29,7 +29,8 @@ import kotlin.coroutines.experimental.intrinsics.* * * 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.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used, + * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt deleted file mode 100644 index 784e36d7cc..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt +++ /dev/null @@ -1,44 +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.CoroutineContext - -/** - * Receiver interface for generic coroutine builders, so that the code inside coroutine has a convenient access - * to its [coroutineContext] and its cancellation status via [isActive]. - */ -public actual interface CoroutineScope { - /** - * Returns `true` when this coroutine is still active (has not completed and was not cancelled yet). - * - * Check this property in long-running computation loops to support cancellation: - * ``` - * while (isActive) { - * // do some computation - * } - * ``` - * - * This property is a shortcut for `coroutineContext[Job]!!.isActive`. See [coroutineContext] and [Job]. - */ - public actual val isActive: Boolean - - /** - * Returns the context of this coroutine. - */ - public actual val coroutineContext: CoroutineContext -} \ No newline at end of file diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt index 2e6f311988..9e36e31da7 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt @@ -65,8 +65,9 @@ import kotlin.coroutines.experimental.* * +-----------+ * ``` * - * A deferred value is a [Job]. A job in the coroutine [context][CoroutineScope.coroutineContext] of [async] builder - * represents the coroutine itself. + * A deferred value is a [Job]. A job in the + * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * of [async] builder represents the coroutine itself. * A deferred value is active while the coroutine is working and cancellation aborts the coroutine when * the coroutine is suspended on a _cancellable_ suspension point by throwing [CancellationException] * or the cancellation cause inside the coroutine. @@ -126,7 +127,8 @@ public actual interface Deferred : Job { * * 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.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used, + * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt index c83e7083d6..42de2d3a69 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt @@ -69,7 +69,9 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn * +-----------+ * ``` * - * A job in the [coroutineContext][CoroutineScope.coroutineContext] represents the coroutine itself. + * A job in the + * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * 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]. * @@ -273,6 +275,25 @@ public actual interface DisposableHandle { // -------------------- CoroutineContext extensions -------------------- +/** + * Returns `true` when the [Job] of the coroutine in this context is still active + * (has not completed and was not cancelled yet). + * + * Check this property in long-running computation loops to support cancellation + * when [CoroutineScope.isActive] is not available: + * + * ``` + * while (coroutineContext.isActive) { + * // do some computation + * } + * ``` + * + * The `coroutineContext.isActive` expression is a shortcut for `coroutineContext[Job]?.isActive == true`. + * See [Job.isActive]. + */ +public actual val CoroutineContext.isActive: Boolean + get() = this[Job]?.isActive == true + /** * 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 diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Promise.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Promise.kt index 9904fe7e76..5e12cba713 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Promise.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Promise.kt @@ -24,7 +24,8 @@ import kotlin.js.* * * 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.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used, + * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) + * of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt index bbe49ce0ae..a884a52f33 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt @@ -17,10 +17,8 @@ package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.intrinsics.* -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED -import kotlin.coroutines.experimental.intrinsics.startCoroutineUninterceptedOrReturn -import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* /** * Runs a given suspending [block] of code inside a coroutine with a specified timeout and throws diff --git a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/PromiseTest.kt b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/PromiseTest.kt index 5f9414c93a..2fa324568d 100644 --- a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/PromiseTest.kt +++ b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/PromiseTest.kt @@ -16,7 +16,8 @@ package kotlinx.coroutines.experimental -import kotlin.js.Promise +import kotlin.coroutines.experimental.* +import kotlin.js.* import kotlin.test.* class PromiseTest : TestBase() { diff --git a/reactive/coroutines-guide-reactive.md b/reactive/coroutines-guide-reactive.md index fd8e30a78f..802ab123e7 100644 --- a/reactive/coroutines-guide-reactive.md +++ b/reactive/coroutines-guide-reactive.md @@ -101,6 +101,7 @@ Let us illustrate it with the following example: ```kotlin @@ -157,6 +158,7 @@ type. ```kotlin @@ -230,6 +232,7 @@ as shown in the following example: import io.reactivex.* import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.reactive.* +import kotlin.coroutines.experimental.* --> ```kotlin @@ -277,6 +280,7 @@ by the publisher, because it is being closed automatically by `consumeEach`: import io.reactivex.* import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.reactive.* +import kotlin.coroutines.experimental.* --> ```kotlin @@ -332,9 +336,10 @@ operator with a buffer of size 1. The subscriber is slow. It takes 500 ms to process each item, which is simulated using `Thread.sleep`. ```kotlin @@ -463,11 +468,10 @@ consuming coroutine in the context of the main thread and use [yield] function t sequence of updates and to release the main thread: ```kotlin @@ -501,11 +505,9 @@ that provides the same logic on top of coroutine channels directly, without going through the bridge to the reactive streams: ```kotlin @@ -613,8 +615,8 @@ into the single `fusedFilterMap` operator: ```kotlin @@ -671,9 +673,9 @@ emits anything. However, we have [select] expression to rescue us in coroutines ```kotlin @@ -740,8 +742,8 @@ operator using the later approach: ```kotlin @@ -754,8 +756,10 @@ fun Publisher>.merge(context: CoroutineContext) = publish(co } ``` -Notice, the use of `coroutineContext` in the invocation of [launch] coroutine builder. It is used to refer -to the [CoroutineScope.coroutineContext] that is provided by [publish] builder. This way, all the coroutines that are +Notice, the use of +[coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) +in the invocation of [launch] coroutine builder. It is used to refer +to the context of the enclosing `publish` coroutine. 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. Moreover, since parent coroutine waits until all children are complete, this implementation fully @@ -1054,7 +1058,6 @@ coroutines for complex pipelines with fan-in and fan-out between multiple worker [Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-unconfined/index.html [yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/yield.html [launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/launch.html -[CoroutineScope.coroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/coroutine-context.html [CommonPool]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-common-pool/index.html [Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/join.html 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 2c8c2d6081..4918b1c2d1 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 @@ -131,7 +131,7 @@ private class PublisherCoroutine( } catch (e: Throwable) { try { if (!cancel(e)) - handleCoroutineException(coroutineContext, e) + handleCoroutineException(context, e) } finally { doLockedSignalCompleted() } @@ -172,7 +172,7 @@ private class PublisherCoroutine( else subscriber.onComplete() } catch (e: Throwable) { - handleCoroutineException(coroutineContext, e) + handleCoroutineException(context, e) } } } finally { diff --git a/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/IntegrationTest.kt b/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/IntegrationTest.kt index 1d3ad803d8..741111f43a 100644 --- a/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/IntegrationTest.kt +++ b/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/IntegrationTest.kt @@ -17,15 +17,13 @@ package kotlinx.coroutines.experimental.reactive import kotlinx.coroutines.experimental.* -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.hamcrest.core.IsNull -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.reactivestreams.Publisher -import kotlin.coroutines.experimental.CoroutineContext +import org.hamcrest.MatcherAssert.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.runner.* +import org.junit.runners.* +import org.reactivestreams.* +import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) class IntegrationTest( diff --git a/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublishTest.kt b/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublishTest.kt index 5c7db3b0a8..7636918ae1 100644 --- a/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublishTest.kt +++ b/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublishTest.kt @@ -16,15 +16,12 @@ package kotlinx.coroutines.experimental.reactive -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert.assertThat -import org.junit.Test -import org.reactivestreams.Subscriber -import org.reactivestreams.Subscription +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import org.reactivestreams.* +import kotlin.coroutines.experimental.* class PublishTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublisherBackpressureTest.kt b/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublisherBackpressureTest.kt index d74eeac27c..68e2b6255b 100644 --- a/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublisherBackpressureTest.kt +++ b/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublisherBackpressureTest.kt @@ -16,12 +16,10 @@ package kotlinx.coroutines.experimental.reactive -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.junit.Test -import org.reactivestreams.Subscriber -import org.reactivestreams.Subscription +import kotlinx.coroutines.experimental.* +import org.junit.* +import org.reactivestreams.* +import kotlin.coroutines.experimental.* class PublisherBackpressureTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublisherSubscriptionSelectTest.kt b/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublisherSubscriptionSelectTest.kt index 647d662829..0d584be795 100644 --- a/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublisherSubscriptionSelectTest.kt +++ b/reactive/kotlinx-coroutines-reactive/src/test/kotlin/kotlinx/coroutines/experimental/reactive/PublisherSubscriptionSelectTest.kt @@ -22,6 +22,7 @@ import org.junit.* import org.junit.Assert.* import org.junit.runner.* import org.junit.runners.* +import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) class PublisherSubscriptionSelectTest(val request: Int) : TestBase() { diff --git a/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/ConvertTest.kt b/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/ConvertTest.kt index 4cc3a42bc3..e0cfa9887f 100644 --- a/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/ConvertTest.kt +++ b/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/ConvertTest.kt @@ -16,21 +16,12 @@ package kotlinx.coroutines.experimental.reactor -import kotlinx.coroutines.experimental.CommonPool -import kotlinx.coroutines.experimental.NonCancellable -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.Unconfined -import kotlinx.coroutines.experimental.async -import kotlinx.coroutines.experimental.channels.produce -import kotlinx.coroutines.experimental.delay -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.reactive.consumeEach -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Assert.fail -import org.junit.Test +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.channels.* +import kotlinx.coroutines.experimental.reactive.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class ConvertTest : TestBase() { class TestException(s: String): RuntimeException(s) diff --git a/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/FluxTest.kt b/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/FluxTest.kt index 74d3cdbeea..8cdf3ded05 100644 --- a/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/FluxTest.kt +++ b/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/FluxTest.kt @@ -16,13 +16,10 @@ package kotlinx.coroutines.experimental.reactor -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import kotlin.coroutines.experimental.* class FluxTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/MonoTest.kt b/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/MonoTest.kt index 4ea6a39cc8..a66d6bffd6 100644 --- a/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/MonoTest.kt +++ b/reactive/kotlinx-coroutines-reactor/src/test/kotlin/kotlinx/coroutines/experimental/reactor/MonoTest.kt @@ -17,18 +17,13 @@ package kotlinx.coroutines.experimental.reactor import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.reactive.awaitFirst -import kotlinx.coroutines.experimental.reactive.awaitLast -import kotlinx.coroutines.experimental.reactive.awaitSingle -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import reactor.core.publisher.Flux -import reactor.core.publisher.Mono -import java.time.Duration.ofMillis +import kotlinx.coroutines.experimental.reactive.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import reactor.core.publisher.* +import java.time.Duration.* +import kotlin.coroutines.experimental.* /** * Tests emitting single item with [mono]. 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 2ede1050d2..3180f4783d 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 @@ -132,7 +132,7 @@ private class RxObservableCoroutine( } catch (e: Throwable) { try { if (!cancel(e)) - handleCoroutineException(coroutineContext, e) + handleCoroutineException(context, e) } finally { doLockedSignalCompleted() } @@ -173,7 +173,7 @@ private class RxObservableCoroutine( else subscriber.onCompleted() } catch (e: Throwable) { - handleCoroutineException(coroutineContext, e) + handleCoroutineException(context, e) } } } finally { diff --git a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/CompletableTest.kt b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/CompletableTest.kt index 584a896b6c..4c1ae5b0cd 100644 --- a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/CompletableTest.kt +++ b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/CompletableTest.kt @@ -16,14 +16,11 @@ package kotlinx.coroutines.experimental.rx1 -import kotlinx.coroutines.experimental.CancellationException -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert.assertThat -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class CompletableTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ConvertTest.kt b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ConvertTest.kt index 9a6cc6e546..6089f0bb44 100644 --- a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ConvertTest.kt +++ b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ConvertTest.kt @@ -17,11 +17,11 @@ package kotlinx.coroutines.experimental.rx1 import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.channels.produce -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert.assertThat -import org.junit.Test +import kotlinx.coroutines.experimental.channels.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class ConvertTest : TestBase() { class TestException(s: String): RuntimeException(s) diff --git a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/IntegrationTest.kt b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/IntegrationTest.kt index d4c0a24f68..c2258694d4 100644 --- a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/IntegrationTest.kt +++ b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/IntegrationTest.kt @@ -17,15 +17,13 @@ package kotlinx.coroutines.experimental.rx1 import kotlinx.coroutines.experimental.* -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.hamcrest.core.IsNull -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import rx.Observable -import kotlin.coroutines.experimental.CoroutineContext +import org.hamcrest.MatcherAssert.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.runner.* +import org.junit.runners.* +import rx.* +import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) class IntegrationTest( diff --git a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableBackpressureTest.kt b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableBackpressureTest.kt index 9213c725b2..0a0a361931 100644 --- a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableBackpressureTest.kt +++ b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableBackpressureTest.kt @@ -16,11 +16,10 @@ package kotlinx.coroutines.experimental.rx1 -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.junit.Test -import rx.Subscriber +import kotlinx.coroutines.experimental.* +import org.junit.* +import rx.* +import kotlin.coroutines.experimental.* class ObservableBackpressureTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableSubscriptionSelectTest.kt b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableSubscriptionSelectTest.kt index 49e7abfafa..960d676bda 100644 --- a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableSubscriptionSelectTest.kt +++ b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableSubscriptionSelectTest.kt @@ -22,6 +22,7 @@ import org.junit.* import org.junit.Assert.* import org.junit.runner.* import org.junit.runners.* +import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) class ObservableSubscriptionSelectTest(val request: Int) : TestBase() { diff --git a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableTest.kt b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableTest.kt index 203d9366cc..cc28bf2e79 100644 --- a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableTest.kt +++ b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/ObservableTest.kt @@ -16,13 +16,10 @@ package kotlinx.coroutines.experimental.rx1 -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import kotlin.coroutines.experimental.* class ObservableTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/SingleTest.kt b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/SingleTest.kt index 4953657e04..23a63b62c9 100644 --- a/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/SingleTest.kt +++ b/reactive/kotlinx-coroutines-rx1/src/test/kotlin/kotlinx/coroutines/experimental/rx1/SingleTest.kt @@ -17,16 +17,13 @@ package kotlinx.coroutines.experimental.rx1 import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.hamcrest.core.IsNull -import org.junit.Assert.assertEquals -import org.junit.Assert.assertThat -import org.junit.Before -import org.junit.Test -import rx.Observable -import rx.Single -import java.util.concurrent.TimeUnit +import kotlinx.coroutines.experimental.CancellationException +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import rx.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* /** * Tests emitting single item with [rxSingle]. 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 ba7c170d1f..cc10e04d7f 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 @@ -133,7 +133,7 @@ private class RxObservableCoroutine( } catch (e: Throwable) { try { if (!cancel(e)) - handleCoroutineException(coroutineContext, e) + handleCoroutineException(context, e) } finally { doLockedSignalCompleted() } @@ -163,7 +163,7 @@ private class RxObservableCoroutine( else subscriber.onComplete() } catch (e: Throwable) { - handleCoroutineException(coroutineContext, e) + handleCoroutineException(context, e) } } } finally { diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-01.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-01.kt index 79dd6af350..9f4f3b2fc0 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-01.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-01.kt @@ -19,6 +19,7 @@ package guide.reactive.basic.example01 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { // create a channel that produces numbers from 1 to 3 with 200ms delays between them diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-02.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-02.kt index c8279a719f..175fdf4e44 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-02.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-02.kt @@ -19,6 +19,7 @@ package guide.reactive.basic.example02 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.reactive.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { // create a publisher that produces numbers from 1 to 3 with 200ms delays between them diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-03.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-03.kt index df620ebdb1..2431f62b6c 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-03.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-03.kt @@ -20,6 +20,7 @@ package guide.reactive.basic.example03 import io.reactivex.* import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.reactive.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { val source = Flowable.range(1, 5) // a range of five numbers diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-04.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-04.kt index f41348afd6..85fcf31ce7 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-04.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-04.kt @@ -20,6 +20,7 @@ package guide.reactive.basic.example04 import io.reactivex.* import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.reactive.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { val source = Flowable.range(1, 5) // a range of five numbers diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-05.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-05.kt index 7528b4ee38..5f96bd3a82 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-05.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-basic-05.kt @@ -17,9 +17,10 @@ // This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit. package guide.reactive.basic.example05 +import io.reactivex.schedulers.* import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.rx2.rxFlowable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.experimental.rx2.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { // coroutine -- fast producer of elements in the context of the main thread 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 dc78fff39b..78bcee36f4 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 @@ -17,11 +17,10 @@ // This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit. package guide.reactive.basic.example08 -import io.reactivex.subjects.BehaviorSubject -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.rx2.consumeEach -import kotlinx.coroutines.experimental.yield +import io.reactivex.subjects.* +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.rx2.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { val subject = BehaviorSubject.create() 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 34f88e3a44..3b48ac2c9a 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 @@ -17,11 +17,9 @@ // This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit. package guide.reactive.basic.example09 -import kotlinx.coroutines.experimental.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.experimental.channels.consumeEach -import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield +import kotlinx.coroutines.experimental.channels.* +import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* fun main(args: Array) = runBlocking { val broadcast = ConflatedBroadcastChannel() diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-02.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-02.kt index 97b6ac20f9..78cd9434b2 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-02.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-02.kt @@ -19,8 +19,8 @@ package guide.reactive.operators.example02 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.reactive.* -import org.reactivestreams.Publisher -import kotlin.coroutines.experimental.CoroutineContext +import org.reactivestreams.* +import kotlin.coroutines.experimental.* fun Publisher.fusedFilterMap( context: CoroutineContext, // the context to execute this coroutine in diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-03.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-03.kt index 160840d61f..bd6d72d4af 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-03.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-03.kt @@ -19,9 +19,9 @@ package guide.reactive.operators.example03 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.reactive.* -import org.reactivestreams.Publisher -import kotlin.coroutines.experimental.CoroutineContext -import kotlinx.coroutines.experimental.selects.whileSelect +import kotlinx.coroutines.experimental.selects.* +import org.reactivestreams.* +import kotlin.coroutines.experimental.* fun Publisher.takeUntil(context: CoroutineContext, other: Publisher) = publish(context) { this@takeUntil.openSubscription().use { thisChannel -> // explicitly open channel to Publisher diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-04.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-04.kt index 5725b2620f..da8c1b595b 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-04.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/guide/example-reactive-operators-04.kt @@ -19,8 +19,8 @@ package guide.reactive.operators.example04 import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.reactive.* -import org.reactivestreams.Publisher -import kotlin.coroutines.experimental.CoroutineContext +import org.reactivestreams.* +import kotlin.coroutines.experimental.* fun Publisher>.merge(context: CoroutineContext) = publish(context) { consumeEach { pub -> // for each publisher received on the source channel diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/CompletableTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/CompletableTest.kt index 59e928ada3..8e8d0ef0f1 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/CompletableTest.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/CompletableTest.kt @@ -16,13 +16,11 @@ package kotlinx.coroutines.experimental.rx2 -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert.assertThat -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class CompletableTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ConvertTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ConvertTest.kt index a16cf5f7d7..d3315a296f 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ConvertTest.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ConvertTest.kt @@ -17,10 +17,10 @@ package kotlinx.coroutines.experimental.rx2 import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.channels.produce -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Test +import kotlinx.coroutines.experimental.channels.* +import org.junit.* +import org.junit.Assert.* +import kotlin.coroutines.experimental.* class ConvertTest : TestBase() { class TestException(s: String): RuntimeException(s) diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/FlowableTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/FlowableTest.kt index 82e4a7651b..1777dc151a 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/FlowableTest.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/FlowableTest.kt @@ -16,13 +16,10 @@ package kotlinx.coroutines.experimental.rx2 -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import kotlin.coroutines.experimental.* class FlowableTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/IntegrationTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/IntegrationTest.kt index 905278d2c5..0b00b27b0c 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/IntegrationTest.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/IntegrationTest.kt @@ -16,16 +16,14 @@ package kotlinx.coroutines.experimental.rx2 -import io.reactivex.Observable +import io.reactivex.* import kotlinx.coroutines.experimental.* -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.hamcrest.core.IsNull -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import kotlin.coroutines.experimental.CoroutineContext +import org.hamcrest.MatcherAssert.* +import org.hamcrest.core.* +import org.junit.* +import org.junit.runner.* +import org.junit.runners.* +import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) class IntegrationTest( diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/MaybeTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/MaybeTest.kt index 451c0fbf20..9eeec99285 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/MaybeTest.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/MaybeTest.kt @@ -16,20 +16,15 @@ package kotlinx.coroutines.experimental.rx2 -import io.reactivex.Maybe -import io.reactivex.Observable -import io.reactivex.functions.Action -import io.reactivex.internal.functions.Functions.ON_ERROR_MISSING -import io.reactivex.internal.functions.Functions.emptyConsumer +import io.reactivex.* +import io.reactivex.functions.* +import io.reactivex.internal.functions.Functions.* import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Before -import org.junit.Test -import java.util.concurrent.TimeUnit +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* class MaybeTest : TestBase() { @Before diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ObservableSubscriptionSelectTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ObservableSubscriptionSelectTest.kt index 24c31afe65..68d7069ad1 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ObservableSubscriptionSelectTest.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ObservableSubscriptionSelectTest.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.selects.* import org.junit.* import org.junit.Assert.* +import kotlin.coroutines.experimental.* class ObservableSubscriptionSelectTest() : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ObservableTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ObservableTest.kt index a2c4c5d77f..ac2e9ebed7 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ObservableTest.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/ObservableTest.kt @@ -16,13 +16,10 @@ package kotlinx.coroutines.experimental.rx2 -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import kotlinx.coroutines.experimental.yield -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert -import org.junit.Test +import kotlinx.coroutines.experimental.* +import org.hamcrest.core.* +import org.junit.* +import kotlin.coroutines.experimental.* class ObservableTest : TestBase() { @Test diff --git a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/SingleTest.kt b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/SingleTest.kt index d7f4a0d920..226d8693ff 100644 --- a/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/SingleTest.kt +++ b/reactive/kotlinx-coroutines-rx2/src/test/kotlin/kotlinx/coroutines/experimental/rx2/SingleTest.kt @@ -16,16 +16,13 @@ package kotlinx.coroutines.experimental.rx2 -import io.reactivex.Observable -import io.reactivex.Single +import io.reactivex.* import kotlinx.coroutines.experimental.* -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsInstanceOf -import org.junit.Assert -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import java.util.concurrent.TimeUnit +import org.hamcrest.core.* +import org.junit.* +import org.junit.Assert.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* /** * Tests emitting single item with [rxSingle]. From 61cafea1390c419e04aa44180941e02ff44c3aaa Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Tue, 13 Mar 2018 18:19:47 +0300 Subject: [PATCH 06/61] Warning about future fail-fast in consumeEach is added to the guide --- reactive/coroutines-guide-reactive.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/reactive/coroutines-guide-reactive.md b/reactive/coroutines-guide-reactive.md index 802ab123e7..76e89bf393 100644 --- a/reactive/coroutines-guide-reactive.md +++ b/reactive/coroutines-guide-reactive.md @@ -216,6 +216,12 @@ We have two of them in this code and that is why we see "Begin" printed twice. In Rx lingo this is called a _cold_ publisher. Many standard Rx operators produce cold streams, too. We can iterate over them from a coroutine, and every subscription produces the same stream of elements. +**WARNING**: It is planned that in the future a second invocation of `consumeEach` method +on an channel that is already being consumed is going to fail fast, that is +immediately throw an `IllegalStateException`. +See [this issue](https://github.com/Kotlin/kotlinx.coroutines/issues/167) +for details. + > Note, that we can replicate the same behaviour that we saw with channels by using Rx [publish](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#publish()) operator and [connect](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/flowables/ConnectableFlowable.html#connect()) From c7d10a4e9a8f6b305d4d694990893003415aa234 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Tue, 13 Mar 2018 18:28:42 +0300 Subject: [PATCH 07/61] Fix references to `coroutineContext` is the docs of all the builders --- .../kotlin/kotlinx/coroutines/experimental/channels/Actor.kt | 2 +- .../kotlinx/coroutines/experimental/channels/Produce.kt | 2 +- .../coroutines/experimental/guava/ListenableFuture.kt | 2 +- .../kotlinx/coroutines/experimental/reactive/Publish.kt | 2 +- .../kotlin/kotlinx/coroutines/experimental/reactor/Mono.kt | 2 +- .../kotlinx/coroutines/experimental/rx1/RxCompletable.kt | 2 +- .../kotlinx/coroutines/experimental/rx1/RxObservable.kt | 2 +- .../kotlin/kotlinx/coroutines/experimental/rx1/RxSingle.kt | 2 +- .../kotlinx/coroutines/experimental/rx2/RxCompletable.kt | 2 +- .../kotlin/kotlinx/coroutines/experimental/rx2/RxFlowable.kt | 5 ++--- .../kotlin/kotlinx/coroutines/experimental/rx2/RxMaybe.kt | 2 +- .../kotlinx/coroutines/experimental/rx2/RxObservable.kt | 2 +- .../kotlin/kotlinx/coroutines/experimental/rx2/RxSingle.kt | 2 +- 13 files changed, 14 insertions(+), 15 deletions(-) diff --git a/core/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 index bb28f358b7..d10bcf618e 100644 --- a/core/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 @@ -56,7 +56,7 @@ interface ActorJob : SendChannel { * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * diff --git a/core/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 index 1565ecde9d..fc86b0aa7a 100644 --- a/core/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 @@ -56,7 +56,7 @@ interface ProducerJob : ReceiveChannel, Job { * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 2eb4c8eb10..81bbbb5d70 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 @@ -28,7 +28,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 4918b1c2d1..63fa447137 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 @@ -40,7 +40,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. 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 0a165d6ec8..612bbb122b 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 @@ -33,7 +33,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 6b5fed958d..6e5431b1df 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 @@ -32,7 +32,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 3180f4783d..e482de2330 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 @@ -40,7 +40,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 b6e6d22ca7..7627f7c274 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 @@ -32,7 +32,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 eb94ab6f4f..114e185bc2 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 @@ -33,7 +33,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * diff --git a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxFlowable.kt b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxFlowable.kt index d4cb3c2592..a1404853aa 100644 --- a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxFlowable.kt +++ b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxFlowable.kt @@ -23,8 +23,7 @@ import kotlinx.coroutines.experimental.DefaultDispatcher import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.channels.ProducerScope import kotlinx.coroutines.experimental.reactive.publish -import kotlin.coroutines.experimental.ContinuationInterceptor -import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.* /** * Creates cold [flowable][Flowable] that will run a given [block] in a coroutine. @@ -42,7 +41,7 @@ import kotlin.coroutines.experimental.CoroutineContext * * 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, + * The [coroutineContext] of the parent coroutine 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. * 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 391107eaa9..e83be120c1 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 @@ -34,7 +34,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 cc10e04d7f..02514957a5 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 @@ -41,7 +41,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * 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 ed214fd5f6..6e63cdb0cf 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 @@ -33,7 +33,7 @@ import kotlin.coroutines.experimental.* * * 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, + * The [coroutineContext] of the parent coroutine may be used, * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. * The parent job may be also explicitly specified using [parent] parameter. * From 45bcb0b3fd129404236a50604078dca6ceed93f7 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 14 Mar 2018 12:48:21 +0300 Subject: [PATCH 08/61] Improve ChannelsConsumeTest to avoid spurious failures due to concurrency --- .../channels/ChannelsConsumeTest.kt | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsConsumeTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsConsumeTest.kt index 18c3234d5e..46c5e71eba 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsConsumeTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsConsumeTest.kt @@ -27,7 +27,7 @@ class ChannelsConsumeTest { private val sourceList = (1..10).toList() // test source with numbers 1..10 - private fun testSource() = produce { + private fun testSource(context: CoroutineContext) = produce(context) { for (i in sourceList) { send(i) } @@ -811,10 +811,10 @@ class ChannelsConsumeTest { fun testZip() { val expect = sourceList.zip(sourceList) { a, b -> a + 2 * b } checkTransform(expect) { ctx -> - zip(testSource(), ctx) { a, b -> a + 2*b } + zip(testSource(ctx), ctx) { a, b -> a + 2*b } } checkTransform(expect) { ctx -> - testSource().zip(this, ctx) { a, b -> a + 2*b } + testSource(ctx).zip(this, ctx) { a, b -> a + 2*b } } } @@ -832,27 +832,28 @@ class ChannelsConsumeTest { expected: ((Throwable?) -> Unit)? = null, terminal: suspend ReceiveChannel.() -> Unit ) { - val src = testSource() - runBlocking { + val src = runBlocking { + val src = testSource(coroutineContext) try { // terminal operation terminal(src) // source must be cancelled at the end of terminal op - assertTrue(src.isClosedForReceive, "Source must be closed") if (expected != null) error("Exception was expected") } catch (e: Throwable) { if (expected == null) throw e expected(e) } + src } + assertTrue(src.isClosedForReceive, "Source must be closed") } private fun checkTerminalCancellation( expected: ((Throwable?) -> Unit)? = null, terminal: suspend ReceiveChannel.() -> Unit ) { - val src = testSource() - runBlocking { + val src = runBlocking { + val src = testSource(coroutineContext) // terminal operation in a separate async context started until the first suspension val d = async(coroutineContext, start = CoroutineStart.UNDISPATCHED) { terminal(src) @@ -869,6 +870,7 @@ class ChannelsConsumeTest { if (expected == null) throw e expected(e) } + src } // source must be cancelled at the end of terminal op even if it was cancelled while in process assertTrue(src.isClosedForReceive, "Source must be closed") @@ -889,8 +891,8 @@ class ChannelsConsumeTest { expect: List, transform: ReceiveChannel.(CoroutineContext) -> ReceiveChannel ) { - val src = testSource() - runBlocking { + val src = runBlocking { + val src = testSource(coroutineContext) // transform val res = transform(src, coroutineContext) // receive nReceive elements from the result @@ -904,6 +906,7 @@ class ChannelsConsumeTest { // then check that result is closed assertEquals(null, res.receiveOrNull(), "Result has unexpected values") } + src } // source must be cancelled when runBlocking processes all the scheduled stuff assertTrue(src.isClosedForReceive, "Source must be closed") From d7d9e2fabf61b768c8bbc6fd875a9060a261b86f Mon Sep 17 00:00:00 2001 From: Sergey Mashkov Date: Fri, 16 Mar 2018 00:01:23 +0300 Subject: [PATCH 09/61] IO: eliminate kotlin.Pair allocations --- .../experimental/io/ByteBufferChannel.kt | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) 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 index 3233a17265..67a45afc3b 100644 --- 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 @@ -164,7 +164,10 @@ internal class ByteBufferChannel( } var _allocated: ReadWriteBufferState.Initial? = null - val (old, newState) = updateState { state -> + var old: ReadWriteBufferState? = null + + val newState = updateStateAndGet { state -> + old = state when { joining != null -> { _allocated?.let { releaseBuffer(it) } @@ -209,7 +212,7 @@ internal class ByteBufferChannel( private fun restoreStateAfterWrite() { var toRelease: ReadWriteBufferState.IdleNonEmpty? = null - val (_, newState) = updateState { + val newState = updateStateAndGet { val writeStopped = it.stopWriting() if (writeStopped is ReadWriteBufferState.IdleNonEmpty && writeStopped.capacity.isEmpty()) { toRelease = writeStopped @@ -324,7 +327,7 @@ internal class ByteBufferChannel( private fun tryReleaseBuffer(forceTermination: Boolean): Boolean { var toRelease: ReadWriteBufferState.Initial? = null - updateState { state -> + updateStateAndGet { state -> toRelease?.let { buffer -> toRelease = null buffer.capacity.resetForWrite() @@ -2283,19 +2286,6 @@ internal class ByteBufferChannel( } } - // todo: replace with atomicfu - private inline fun updateState(block: (ReadWriteBufferState) -> ReadWriteBufferState?): - Pair = update({ state }, State, block) - - // todo: replace with atomicfu - private inline fun update(getter: () -> T, updater: AtomicReferenceFieldUpdater, block: (old: T) -> T?): Pair { - 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 From 783171600e0a9b58ff218776178798c18f6c7aee Mon Sep 17 00:00:00 2001 From: Sergey Mashkov Date: Wed, 21 Mar 2018 10:12:40 +0300 Subject: [PATCH 10/61] IO: introduce non-flushing joinTo --- .../kotlin/benchmarks/ChannelCopyBenchmark.kt | 10 +++++----- .../experimental/io/ByteBufferChannel.kt | 20 ++++++++++--------- .../experimental/io/ByteReadChannel.kt | 10 +++++----- .../experimental/io/ByteBufferChannelTest.kt | 15 ++++++++++++++ 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelCopyBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelCopyBenchmark.kt index cf2e043a8c..8a41d03810 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/ChannelCopyBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelCopyBenchmark.kt @@ -169,7 +169,7 @@ open class ChannelCopyBenchmark { val pIn = ByteChannel(true) val pOut = ByteChannel(true) - launch(coroutineContext) { + launch(this.coroutineContext) { pOut.joinTo(pIn, true) } @@ -193,7 +193,7 @@ open class ChannelCopyBenchmark { val pIn = ByteChannel(true) val pOut = ByteChannel(true) - launch(coroutineContext) { + launch(this.coroutineContext) { pOut.joinTo(pIn, true) } @@ -217,7 +217,7 @@ open class ChannelCopyBenchmark { val pIn = ByteChannel(true) val pOut = ByteChannel(true) - launch(coroutineContext) { + launch(this.coroutineContext) { pOut.copyTo(pIn) pIn.close() } @@ -242,7 +242,7 @@ open class ChannelCopyBenchmark { val pIn = ByteChannel(true) val pOut = ByteChannel(true) - launch(coroutineContext) { + launch(this.coroutineContext) { pOut.copyTo(pIn) pIn.close() } @@ -298,7 +298,7 @@ open class ChannelCopyBenchmark { @Benchmark fun runBlockingAndLaunch() = runBlocking { - launch(coroutineContext) { + launch(this.coroutineContext) { yield() } 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 index 67a45afc3b..59dd21a7d6 100644 --- 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 @@ -282,10 +282,10 @@ internal class ByteBufferChannel( } } - private fun setupDelegateTo(delegate: ByteBufferChannel, delegateClose: Boolean): JoiningState { + private fun setupDelegateTo(delegate: ByteBufferChannel, delegateClose: Boolean, delegateFlush: Boolean): JoiningState { require(this !== delegate) - val joined = JoiningState(delegate, delegateClose) + val joined = JoiningState(delegate, delegateClose, delegateFlush) delegate.writeByteOrder = writeByteOrder this.joining = joined @@ -293,7 +293,7 @@ internal class ByteBufferChannel( if (alreadyClosed != null) { if (alreadyClosed.cause != null) delegate.close(alreadyClosed.cause) else if (delegateClose && state === ReadWriteBufferState.Terminated) delegate.close() - else delegate.flush() + else if (delegateFlush) delegate.flush() } else { flush() } @@ -1174,7 +1174,7 @@ internal class ByteBufferChannel( } } - internal suspend fun joinFrom(src: ByteBufferChannel, delegateClose: Boolean) { + internal suspend fun joinFrom(src: ByteBufferChannel, delegateClose: Boolean, delegateFlush: Boolean) { if (src.closed != null && src.state === ReadWriteBufferState.Terminated) { if (delegateClose) close(src.closed!!.cause) return @@ -1184,7 +1184,7 @@ internal class ByteBufferChannel( return } - val joined = src.setupDelegateTo(this, delegateClose) + val joined = src.setupDelegateTo(this, delegateClose, delegateFlush) if (src.tryCompleteJoining(joined)) { return src.awaitClose() } @@ -1198,7 +1198,9 @@ internal class ByteBufferChannel( if (delegateClose && src.isClosedForRead) { close() } else { - flush() + if (joined.delegateFlush) { + flush() + } src.awaitClose() } } @@ -1315,10 +1317,10 @@ internal class ByteBufferChannel( val writing = joined.delegatedTo.state.let { it is ReadWriteBufferState.Writing || it is ReadWriteBufferState.ReadingWriting } if (closed.cause != null || !writing) { joined.delegatedTo.close(closed.cause) - } else { + } else if (joined.delegateFlush) { joined.delegatedTo.flush() } - } else { + } else if (joined.delegateFlush) { joined.delegatedTo.flush() } @@ -2318,7 +2320,7 @@ internal class ByteBufferChannel( } } - internal class JoiningState(val delegatedTo: ByteBufferChannel, val delegateClose: Boolean) { + internal class JoiningState(val delegatedTo: ByteBufferChannel, val delegateClose: Boolean, val delegateFlush: Boolean) { private val _closeWaitJob = atomic(null) private val closed = atomic(0) 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 index 656d18e3c5..37e4e25394 100644 --- 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 @@ -167,21 +167,21 @@ public interface ByteReadChannel { typealias ConsumeEachBufferVisitor = (buffer: java.nio.ByteBuffer, last: Boolean) -> Boolean -suspend fun ByteReadChannel.joinTo(dst: ByteWriteChannel, closeOnEnd: Boolean) { +suspend fun ByteReadChannel.joinTo(dst: ByteWriteChannel, closeOnEnd: Boolean, flushOnEnd: Boolean = true) { require(dst !== this) if (this is ByteBufferChannel && dst is ByteBufferChannel) { - return dst.joinFrom(this, closeOnEnd) + return dst.joinFrom(this, closeOnEnd, flushOnEnd) } - return joinToImplSuspend(dst, closeOnEnd) + return joinToImplSuspend(dst, closeOnEnd, flushOnEnd) } -private suspend fun ByteReadChannel.joinToImplSuspend(dst: ByteWriteChannel, close: Boolean) { +private suspend fun ByteReadChannel.joinToImplSuspend(dst: ByteWriteChannel, close: Boolean, flush: Boolean) { copyToImpl(dst, Long.MAX_VALUE) if (close) { dst.close() - } else { + } else if (flush) { dst.flush() } } 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 index 091e29c1c6..1ff9673ffc 100644 --- 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 @@ -1425,6 +1425,21 @@ class ByteBufferChannelTest : TestBase() { } } + @Test + fun testJoinToNoFlush() = runTest { + val src = ByteChannel(false) + launch(coroutineContext) { + src.joinTo(ch, closeOnEnd = false, flushOnEnd = false) + assertEquals(0, ch.availableForRead) + ch.flush() + assertEquals(4, ch.availableForRead) + } + yield() + + src.writeInt(777) + src.close() + } + @Test fun testReadBlock() = runTest { var bytesRead = 0L From 740c485b23c88683a4f4297890dc26ec559351a8 Mon Sep 17 00:00:00 2001 From: Venkat Peri Date: Wed, 21 Mar 2018 10:00:55 -0400 Subject: [PATCH 11/61] Fail with proper message if JDK_16 is not set Fixes #291 --- README.md | 11 +++++++++++ core/kotlinx-coroutines-core/build.gradle | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bbca5dcaec..1482a25f6c 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,17 @@ To avoid field overloading by type during obfuscation, add this to your config: } ``` +## Building + +This library is built with Gradle. To build it, use `./gradlew build`. +You can import this project into IDEA, but you have to delegate build actions +to Gradle (in Preferences -> Build, Execution, Deployment -> Build Tools -> Gradle -> Runner) + +### Requirements + +* JDK >= 1.8 referred to by the `JAVA_HOME` environment variable. +* JDK 1.6 referred to by the `JDK_16` environment variable. + ## Contributions and releases All development (both new features and bug fixes) is performed in `develop` branch. diff --git a/core/kotlinx-coroutines-core/build.gradle b/core/kotlinx-coroutines-core/build.gradle index b7ffdb6b24..ca4131d6fb 100644 --- a/core/kotlinx-coroutines-core/build.gradle +++ b/core/kotlinx-coroutines-core/build.gradle @@ -2,8 +2,21 @@ dependencies { testCompile "com.devexperts.lincheck:core:$lincheck_version" } +task checkJdk16() { + // only fail w/o JDK_16 when actually trying to compile, not during project setup phase + doLast { + if (!System.env.JDK_16) { + throw new GradleException("JDK_16 environment variable is not defined. " + + "Can't build against JDK 1.6 runtime and run JDK 1.6 compatibility tests. " + + "Please ensure JDK 1.6 is installed and that JDK_16 points to it.") + } + } +} + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { kotlinOptions.jdkHome = System.env.JDK_16 + // only fail when actually trying to compile, not during project setup phase + dependsOn(checkJdk16) } tasks.withType(Test) { @@ -24,7 +37,8 @@ task lockFreedomTest(type: Test, dependsOn: testClasses) { include '**/*LFTest.*' } -task jdk16Test(type: Test, dependsOn: testClasses) { + +task jdk16Test(type: Test, dependsOn: [testClasses, checkJdk16]) { executable = "$System.env.JDK_16/bin/java" exclude '**/*LinearizabilityTest.*' exclude '**/*LFTest.*' @@ -41,4 +55,4 @@ task testsJar(type: Jar, dependsOn: testClasses) { artifacts { archives testsJar -} \ No newline at end of file +} From 26f4b9ecd1bdd03299b668541f7c7c9ecaf9ab7c Mon Sep 17 00:00:00 2001 From: Marko Devcic Date: Tue, 20 Mar 2018 20:28:52 +0100 Subject: [PATCH 12/61] Add extension to ExecutorService to return closeable CoroutineDispatcher Fixes #278 --- .../coroutines/experimental/Executors.kt | 20 ++++++++++++++++++- .../coroutines/experimental/ExecutorsTest.kt | 11 ++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt index a55dd314a6..545300fabe 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt @@ -16,12 +16,26 @@ package kotlinx.coroutines.experimental +import java.io.Closeable import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService import java.util.concurrent.RejectedExecutionException import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit import kotlin.coroutines.experimental.CoroutineContext +/** + * [CoroutineDispatcher] that implements [Closeable] + */ +abstract class CloseableCoroutineDispatcher: CoroutineDispatcher(), Closeable + +/** + * Converts an instance of [ExecutorService] to an implementation of [CloseableCoroutineDispatcher]. + */ +public fun ExecutorService.asCoroutineDispatcher(): CloseableCoroutineDispatcher = + // we know that an implementation of Executor.asCoroutineDispatcher actually returns a closeable one + (this as Executor).asCoroutineDispatcher() as CloseableCoroutineDispatcher + /** * Converts an instance of [Executor] to an implementation of [CoroutineDispatcher]. * @suppress **Deprecated**: Renamed to [asCoroutineDispatcher]. @@ -42,7 +56,7 @@ private class ExecutorCoroutineDispatcher(override val executor: Executor) : Exe /** * @suppress **This is unstable API and it is subject to change.** */ -public abstract class ExecutorCoroutineDispatcherBase : CoroutineDispatcher(), Delay { +public abstract class ExecutorCoroutineDispatcherBase : CloseableCoroutineDispatcher(), Delay { /** * @suppress **This is unstable API and it is subject to change.** */ @@ -77,6 +91,10 @@ public abstract class ExecutorCoroutineDispatcherBase : CoroutineDispatcher(), D return DefaultExecutor.invokeOnTimeout(time, unit, block) } + override fun close() { + (executor as? ExecutorService)?.shutdown() + } + override fun toString(): String = executor.toString() override fun equals(other: Any?): Boolean = other is ExecutorCoroutineDispatcherBase && other.executor === executor override fun hashCode(): Int = System.identityHashCode(executor) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/ExecutorsTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/ExecutorsTest.kt index 2a9bbb7856..d6a82680f3 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/ExecutorsTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/ExecutorsTest.kt @@ -66,4 +66,15 @@ class ExecutorsTest : TestBase() { ctx1.close() ctx2.close() } + + @Test + fun testShutdownExecutorService() { + val executorService = Executors.newSingleThreadExecutor { r -> Thread(r, "TestExecutor") } + val dispatcher = executorService.asCoroutineDispatcher() + runBlocking (dispatcher) { + checkThreadName("TestExecutor") + } + dispatcher.close() + check(executorService.isShutdown) + } } \ No newline at end of file From 8e3841882314c87195cb63b2eee2246223c8d35c Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Tue, 3 Apr 2018 10:35:46 +0300 Subject: [PATCH 13/61] Explicitly mention that jcenter must be in a list of repos Fixes #307 --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index bbca5dcaec..1752c0d8d1 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,14 @@ buildscript { } ``` +Make sure that you have either `jcenter()` or `mavenCentral()` in the list of repositories: + +``` +repository { + jcenter() +} +``` + ### Kotlin/JS Use `org.jetbrains.kotlinx:kotlinx-coroutines-core-js:` artifact in your Gradle/Maven dependencies From 905a512625ad96dcd45c789f983babd93dfde011 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 4 Apr 2018 09:59:33 +0300 Subject: [PATCH 14/61] typo fixed --- gradle.properties | 2 +- gradle/test-mocha-js.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index efab101cad..dde178fd12 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,6 @@ npm_version = 5.7.1 mocha_version = 4.1.0 mocha_headless_chrome_version = 1.8.2 mocha_teamcity_reporter_version = 2.2.2 -source_map_suport_version = 0.5.3 +source_map_support_version = 0.5.3 kotlin.incremental.multiplatform=true \ No newline at end of file diff --git a/gradle/test-mocha-js.gradle b/gradle/test-mocha-js.gradle index 42023b8648..c2f75034a3 100644 --- a/gradle/test-mocha-js.gradle +++ b/gradle/test-mocha-js.gradle @@ -3,7 +3,7 @@ task installDependenciesMochaNode(type: NpmTask, dependsOn: [npmInstall]) { args = ['install', "mocha@$mocha_version", - "source-map-support@$source_map_suport_version", + "source-map-support@$source_map_support_version", '--no-save'] if (project.hasProperty("teamcity")) args += [ "mocha-teamcity-reporter@$mocha_teamcity_reporter_version"] From e1a565201f3882afb86563b103164557bc9ff8c7 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 4 Apr 2018 10:31:08 +0300 Subject: [PATCH 15/61] Extracted compilation configs into separate build files --- build.gradle | 59 ++---------------------------------- gradle/compile-all.gradle | 19 ++++++++++++ gradle/compile-common.gradle | 11 +++++++ gradle/compile-js.gradle | 20 ++++++++++++ gradle/compile-jvm.gradle | 14 +++++++++ 5 files changed, 66 insertions(+), 57 deletions(-) create mode 100644 gradle/compile-all.gradle create mode 100644 gradle/compile-common.gradle create mode 100644 gradle/compile-js.gradle create mode 100644 gradle/compile-jvm.gradle diff --git a/build.gradle b/build.gradle index 922a3ec0db..12af8d2ccd 100644 --- a/build.gradle +++ b/build.gradle @@ -46,63 +46,8 @@ static def platformLib(base, platform) { configure(subprojects.findAll { !sourceless.contains(it.name) }) { def platform = platformOf(it) - apply plugin: "kotlin-platform-$platform" - - if (platform == "jvm") { - sourceCompatibility = 1.6 - targetCompatibility = 1.6 - } - - kotlin.experimental.coroutines "enable" - - if (platform == "js") { - tasks.withType(compileKotlin2Js.getClass()) { - kotlinOptions { - moduleKind = "umd" - sourceMap = true - metaInfo = true - // drop -js suffix from outputFile - def baseName = project.name - "-js" - outputFile = new File(outputFile.parent, baseName + ".js") - } - } - } - - tasks.withType(Test) { - testLogging { - showStandardStreams = true - events "passed", "failed" - } - def stressTest = project.properties['stressTest'] - if (stressTest != null) systemProperties['stressTest'] = stressTest - } - - repositories { - jcenter() - maven { url "http://kotlin.bintray.com/kotlinx" } - maven { url "https://dl.bintray.com/devexperts/Maven/" } - } - - def kotlin_stdlib = platformLib("kotlin-stdlib", platform) - def kotlin_test = platformLib("kotlin-test", platform) - - dependencies { - compile "org.jetbrains.kotlin:$kotlin_stdlib:$kotlin_version" - testCompile "org.jetbrains.kotlin:$kotlin_test:$kotlin_version" - } - - if (platform == "common") { - dependencies { - testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version" - } - } - - if (platform == "jvm") { - dependencies { - testCompile "junit:junit:$junit_version" - testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" - } - } + apply from: rootProject.file("gradle/compile-${platform}.gradle") + apply from: rootProject.file("gradle/compile-all.gradle") } // --------------- Configure sub-projects that are part of the library --------------- diff --git a/gradle/compile-all.gradle b/gradle/compile-all.gradle new file mode 100644 index 0000000000..e4c0c9988d --- /dev/null +++ b/gradle/compile-all.gradle @@ -0,0 +1,19 @@ + +// Shared configuration to compile all modules + +kotlin.experimental.coroutines "enable" + +repositories { + jcenter() + maven { url "http://kotlin.bintray.com/kotlinx" } + maven { url "https://dl.bintray.com/devexperts/Maven/" } +} + +tasks.withType(Test) { + testLogging { + showStandardStreams = true + events "passed", "failed" + } + def stressTest = project.properties['stressTest'] + if (stressTest != null) systemProperties['stressTest'] = stressTest +} diff --git a/gradle/compile-common.gradle b/gradle/compile-common.gradle new file mode 100644 index 0000000000..219a8a8fe8 --- /dev/null +++ b/gradle/compile-common.gradle @@ -0,0 +1,11 @@ + +// Platform-specific configuration to compile common modules + +apply plugin: 'kotlin-platform-common' + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version" +} + diff --git a/gradle/compile-js.gradle b/gradle/compile-js.gradle new file mode 100644 index 0000000000..9b5755a378 --- /dev/null +++ b/gradle/compile-js.gradle @@ -0,0 +1,20 @@ + +// Platform-specific configuration to compile JS modules + +apply plugin: 'kotlin-platform-js' + +tasks.withType(compileKotlin2Js.getClass()) { + kotlinOptions { + moduleKind = "umd" + sourceMap = true + metaInfo = true + // drop -js suffix from outputFile + def baseName = project.name - "-js" + outputFile = new File(outputFile.parent, baseName + ".js") + } +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version" +} diff --git a/gradle/compile-jvm.gradle b/gradle/compile-jvm.gradle new file mode 100644 index 0000000000..fe00c2ffaa --- /dev/null +++ b/gradle/compile-jvm.gradle @@ -0,0 +1,14 @@ + +// Platform-specific configuration to compile JVM modules + +apply plugin: 'kotlin-platform-jvm' + +sourceCompatibility = 1.6 +targetCompatibility = 1.6 + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" + testCompile "junit:junit:$junit_version" +} From 31452903041de4e273d640d5f4014ac694a494e5 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 11 Apr 2018 13:58:19 +0300 Subject: [PATCH 16/61] Restructure build logic, prepare for native --- build.gradle | 40 +++++------------ gradle.properties | 6 ++- gradle/atomicfu-common.gradle | 4 ++ gradle/atomicfu-js.gradle | 4 ++ gradle/atomicfu-jvm.gradle | 22 +++++++++ gradle/atomicfu-native.gradle | 6 +++ gradle/compile-all.gradle | 19 -------- gradle/compile-common.gradle | 6 +++ gradle/compile-js.gradle | 21 +++++++-- gradle/compile-jvm.gradle | 17 +++++++ gradle/compile-native.gradle | 37 +++++++++++++++ settings.gradle | 85 ++++++++++++++++------------------- 12 files changed, 167 insertions(+), 100 deletions(-) create mode 100644 gradle/atomicfu-common.gradle create mode 100644 gradle/atomicfu-js.gradle create mode 100644 gradle/atomicfu-jvm.gradle create mode 100644 gradle/atomicfu-native.gradle delete mode 100644 gradle/compile-all.gradle create mode 100644 gradle/compile-native.gradle diff --git a/build.gradle b/build.gradle index 12af8d2ccd..32632ff7ad 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,4 @@ allprojects { - group = 'org.jetbrains.kotlinx' def deployVersion = properties['DeployVersion'] if (deployVersion != null) version = deployVersion } @@ -13,12 +12,14 @@ buildscript { } repositories { jcenter() - maven { url "http://kotlin.bintray.com/kotlinx" } - maven { url "http://kotlin.bintray.com/kotlin-dev" } + maven { url "https://kotlin.bintray.com/kotlinx" } + maven { url "https://kotlin.bintray.com/kotlin-dev" } + maven { url "https://jetbrains.bintray.com/kotlin-native-dependencies" } maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_version" classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicFU_version" classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$bintray_version" @@ -36,6 +37,7 @@ def sourceless = ['site'] static def platformOf(project) { if (project.name.endsWith("-common")) return "common" if (project.name.endsWith("-js")) return "js" + if (project.name.endsWith("-native")) return "native" return "jvm" } @@ -47,37 +49,16 @@ static def platformLib(base, platform) { configure(subprojects.findAll { !sourceless.contains(it.name) }) { def platform = platformOf(it) apply from: rootProject.file("gradle/compile-${platform}.gradle") - apply from: rootProject.file("gradle/compile-all.gradle") } // --------------- Configure sub-projects that are part of the library --------------- def internal = sourceless + ['benchmarks', 'knit', 'js-stub'] -// configure atomicfu for JVM modules -configure(subprojects.findAll { !internal.contains(it.name) && platformOf(it) == "jvm" }) { - apply plugin: 'kotlinx-atomicfu' - - dependencies { - compileOnly "org.jetbrains.kotlinx:atomicfu:$atomicFU_version" - testCompile "org.jetbrains.kotlinx:atomicfu:$atomicFU_version" - } - - atomicFU { - inputFiles = sourceSets.main.output.classesDirs - outputDir = file("$buildDir/classes-atomicfu/main") - classPath = sourceSets.main.runtimeClasspath - } - - jar { - mainSpec.sourcePaths.clear() // hack to clear existing paths - from files(atomicFU.outputs, sourceSets.main.output.resourcesDir) - } - - test { - classpath = files(configurations.testRuntime, atomicFU.outputs, sourceSets.test.output.classesDirs, - sourceSets.main.output.resourcesDir) - } +// configure atomicfu +configure(subprojects.findAll { !internal.contains(it.name) }) { + def platform = platformOf(it) + apply from: rootProject.file("gradle/atomicfu-${platform}.gradle") } // configure dependencies on core @@ -100,7 +81,8 @@ configure(subprojects.findAll { !internal.contains(it.name) && it.name != 'kotli // --------------- Configure sub-projects that are published --------------- -def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js'] +// todo: native is not published yet +def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js', 'kotlinx-coroutines-core-native'] def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/" def core_docs_file = "$projectDir/core/kotlinx-coroutines-core/build/dokka/kotlinx-coroutines-core/package-list" diff --git a/gradle.properties b/gradle.properties index dde178fd12..16e5ff92ee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,10 @@ version = 0.22.5-SNAPSHOT +group = org.jetbrains.kotlinx -kotlin_version = 1.2.30 +kotlin_version = 1.2.31 +kotlin_native_version = 0.7-dev-1436 junit_version = 4.12 -atomicFU_version = 0.9.2 +atomicFU_version = 0.10.0-alpha html_version = 0.6.8 lincheck_version=1.9 dokka_version = 0.9.16-dev-mpp-hacks-1 diff --git a/gradle/atomicfu-common.gradle b/gradle/atomicfu-common.gradle new file mode 100644 index 0000000000..bf524c18ce --- /dev/null +++ b/gradle/atomicfu-common.gradle @@ -0,0 +1,4 @@ + +dependencies { + compile "org.jetbrains.kotlinx:atomicfu-common:$atomicFU_version" +} \ No newline at end of file diff --git a/gradle/atomicfu-js.gradle b/gradle/atomicfu-js.gradle new file mode 100644 index 0000000000..05f09f46d4 --- /dev/null +++ b/gradle/atomicfu-js.gradle @@ -0,0 +1,4 @@ + +dependencies { + compile "org.jetbrains.kotlinx:atomicfu-js:$atomicFU_version" +} diff --git a/gradle/atomicfu-jvm.gradle b/gradle/atomicfu-jvm.gradle new file mode 100644 index 0000000000..a812931d6d --- /dev/null +++ b/gradle/atomicfu-jvm.gradle @@ -0,0 +1,22 @@ +apply plugin: 'kotlinx-atomicfu' + +dependencies { + compileOnly "org.jetbrains.kotlinx:atomicfu:$atomicFU_version" + testCompile "org.jetbrains.kotlinx:atomicfu:$atomicFU_version" +} + +atomicFU { + inputFiles = sourceSets.main.output.classesDirs + outputDir = file("$buildDir/classes-atomicfu/main") + classPath = sourceSets.main.runtimeClasspath +} + +jar { + mainSpec.sourcePaths.clear() // hack to clear existing paths + from files(atomicFU.outputs, sourceSets.main.output.resourcesDir) +} + +test { + classpath = files(configurations.testRuntime, atomicFU.outputs, sourceSets.test.output.classesDirs, + sourceSets.main.output.resourcesDir) +} diff --git a/gradle/atomicfu-native.gradle b/gradle/atomicfu-native.gradle new file mode 100644 index 0000000000..75f01286ba --- /dev/null +++ b/gradle/atomicfu-native.gradle @@ -0,0 +1,6 @@ + +// todo: this does not work in Kotlin/Native + +//dependencies { +// compile "org.jetbrains.kotlinx:atomicfu-native:$atomicFU_version" +//} diff --git a/gradle/compile-all.gradle b/gradle/compile-all.gradle deleted file mode 100644 index e4c0c9988d..0000000000 --- a/gradle/compile-all.gradle +++ /dev/null @@ -1,19 +0,0 @@ - -// Shared configuration to compile all modules - -kotlin.experimental.coroutines "enable" - -repositories { - jcenter() - maven { url "http://kotlin.bintray.com/kotlinx" } - maven { url "https://dl.bintray.com/devexperts/Maven/" } -} - -tasks.withType(Test) { - testLogging { - showStandardStreams = true - events "passed", "failed" - } - def stressTest = project.properties['stressTest'] - if (stressTest != null) systemProperties['stressTest'] = stressTest -} diff --git a/gradle/compile-common.gradle b/gradle/compile-common.gradle index 219a8a8fe8..b09589cb1a 100644 --- a/gradle/compile-common.gradle +++ b/gradle/compile-common.gradle @@ -3,9 +3,15 @@ apply plugin: 'kotlin-platform-common' +kotlin.experimental.coroutines "enable" + dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version" } +repositories { + jcenter() + maven { url "https://kotlin.bintray.com/kotlinx" } +} diff --git a/gradle/compile-js.gradle b/gradle/compile-js.gradle index 9b5755a378..59c47d24ea 100644 --- a/gradle/compile-js.gradle +++ b/gradle/compile-js.gradle @@ -3,18 +3,31 @@ apply plugin: 'kotlin-platform-js' +kotlin.experimental.coroutines "enable" + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version" +} + +repositories { + jcenter() + maven { url "https://kotlin.bintray.com/kotlinx" } +} + tasks.withType(compileKotlin2Js.getClass()) { kotlinOptions { moduleKind = "umd" sourceMap = true metaInfo = true + } +} + +compileKotlin2Js { + kotlinOptions { // drop -js suffix from outputFile def baseName = project.name - "-js" outputFile = new File(outputFile.parent, baseName + ".js") } } -dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" - testCompile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version" -} diff --git a/gradle/compile-jvm.gradle b/gradle/compile-jvm.gradle index fe00c2ffaa..74030e583d 100644 --- a/gradle/compile-jvm.gradle +++ b/gradle/compile-jvm.gradle @@ -6,9 +6,26 @@ apply plugin: 'kotlin-platform-jvm' sourceCompatibility = 1.6 targetCompatibility = 1.6 +kotlin.experimental.coroutines "enable" + dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testCompile "junit:junit:$junit_version" } + +repositories { + jcenter() + maven { url "https://kotlin.bintray.com/kotlinx" } + maven { url "https://dl.bintray.com/devexperts/Maven/" } +} + +tasks.withType(Test) { + testLogging { + showStandardStreams = true + events "passed", "failed" + } + def stressTest = project.properties['stressTest'] + if (stressTest != null) systemProperties['stressTest'] = stressTest +} diff --git a/gradle/compile-native.gradle b/gradle/compile-native.gradle new file mode 100644 index 0000000000..6de11bba6a --- /dev/null +++ b/gradle/compile-native.gradle @@ -0,0 +1,37 @@ +apply plugin: 'konan' + +repositories { + jcenter() + maven { url "https://kotlin.bintray.com/kotlinx" } +} + +def libraryName = project.name +def testProgramName = libraryName + "-test" + +konanArtifacts { + library(libraryName, targets: ["ios_arm64", "ios_x64", "macos_x64"]) { + artifactName libraryName.replace('-', '_') + enableMultiplatform true + dependencies { + "artifact$libraryName" "org.jetbrains.kotlinx:atomicfu-native:$atomicFU_version" + } + } + // TODO: THIS IS A WORKAROUND: Cannot do tests together with publishing in Kotlin/Native + if (!rootProject.properties["publish"]) { + program(testProgramName, targets: ["macos_x64"]) { + srcDir 'src/test/kotlin' + commonSourceSet 'test' + libraries { + artifact libraryName + } + extraOpts '-tr' + } + } +} + +task test(dependsOn: run) + +// TODO: THIS IS A WORKAROUND: Cannot do tests together with publishing in Kotlin/Native +if (rootProject.properties["publish"]) { + publishToMavenLocal.dependsOn(build) +} diff --git a/settings.gradle b/settings.gradle index b7657f3c74..ebe9b165ee 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,48 +1,41 @@ rootProject.name = 'kotlinx.coroutines' -include 'kotlinx-coroutines-core-common' - -include 'kotlinx-coroutines-core' -include 'kotlinx-coroutines-io' - -include 'kotlinx-coroutines-guava' -include 'kotlinx-coroutines-jdk8' -include 'kotlinx-coroutines-nio' -include 'kotlinx-coroutines-quasar' - -include 'kotlinx-coroutines-reactive' -include 'kotlinx-coroutines-reactor' -include 'kotlinx-coroutines-rx1' -include 'kotlinx-coroutines-rx2' -include 'kotlinx-coroutines-rx-example' - -include 'kotlinx-coroutines-android' -include 'kotlinx-coroutines-javafx' -include 'kotlinx-coroutines-swing' - -include 'kotlinx-coroutines-core-js' -include 'js-stub' -include 'example-frontend-js' - -include 'benchmarks' -include 'knit' -include 'site' - -project(':kotlinx-coroutines-core-common').projectDir = file('common/kotlinx-coroutines-core-common') -project(':kotlinx-coroutines-core').projectDir = file('core/kotlinx-coroutines-core') -project(':kotlinx-coroutines-io').projectDir = file('core/kotlinx-coroutines-io') -project(':kotlinx-coroutines-guava').projectDir = file('integration/kotlinx-coroutines-guava') -project(':kotlinx-coroutines-jdk8').projectDir = file('integration/kotlinx-coroutines-jdk8') -project(':kotlinx-coroutines-nio').projectDir = file('integration/kotlinx-coroutines-nio') -project(':kotlinx-coroutines-quasar').projectDir = file('integration/kotlinx-coroutines-quasar') -project(':kotlinx-coroutines-reactive').projectDir = file('reactive/kotlinx-coroutines-reactive') -project(':kotlinx-coroutines-reactor').projectDir = file('reactive/kotlinx-coroutines-reactor') -project(':kotlinx-coroutines-rx1').projectDir = file('reactive/kotlinx-coroutines-rx1') -project(':kotlinx-coroutines-rx2').projectDir = file('reactive/kotlinx-coroutines-rx2') -project(':kotlinx-coroutines-rx-example').projectDir = file('reactive/kotlinx-coroutines-rx-example') -project(':kotlinx-coroutines-android').projectDir = file('ui/kotlinx-coroutines-android') -project(':kotlinx-coroutines-javafx').projectDir = file('ui/kotlinx-coroutines-javafx') -project(':kotlinx-coroutines-swing').projectDir = file('ui/kotlinx-coroutines-swing') -project(':kotlinx-coroutines-core-js').projectDir = file('js/kotlinx-coroutines-core-js') -project(':js-stub').projectDir = file('js/js-stub') -project(':example-frontend-js').projectDir = file('js/example-frontend-js') +def module(String path) { + int i = path.lastIndexOf('/') + def name = path.substring(i + 1) + include(name) + project(":$name").projectDir = file(path) +} + +// --------------------------- + +include('benchmarks') +include('knit') +include('site') + +module('common/kotlinx-coroutines-core-common') + +module('core/kotlinx-coroutines-core') +module('core/kotlinx-coroutines-io') + +module('integration/kotlinx-coroutines-guava') +module('integration/kotlinx-coroutines-jdk8') +module('integration/kotlinx-coroutines-nio') +module('integration/kotlinx-coroutines-quasar') + +module('reactive/kotlinx-coroutines-reactive') +module('reactive/kotlinx-coroutines-reactor') +module('reactive/kotlinx-coroutines-rx1') +module('reactive/kotlinx-coroutines-rx2') +module('reactive/kotlinx-coroutines-rx-example') + +module('ui/kotlinx-coroutines-android') +module('ui/kotlinx-coroutines-javafx') +module('ui/kotlinx-coroutines-swing') + +module('js/kotlinx-coroutines-core-js') +module('js/js-stub') +module('js/example-frontend-js') + +//module('native/kotlinx-coroutines-core-native') + From aa461cf9ef276d8940977988df3ac74598c47252 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 11 Apr 2018 13:20:29 +0300 Subject: [PATCH 17/61] Minimize cut-and-pasted code between JS and JVM --- .../experimental/AbstractContinuation.kt | 7 +- .../experimental/Annotations.common.kt | 29 + .../experimental/Builders.common.kt | 66 +- .../experimental/CancellableContinuation.kt | 52 +- .../coroutines/experimental/CommonBuilders.kt | 35 - .../CommonCancellableContinuation.kt | 49 - .../experimental/CommonCoroutineDispatcher.kt | 32 - .../coroutines/experimental/CommonDeferred.kt | 35 - .../coroutines/experimental/CommonJob.kt | 101 -- .../experimental/CommonNonCancellable.kt | 31 - .../experimental/CompletableDeferred.kt | 13 +- .../experimental/CompletedExceptionally.kt | 4 +- ...Handler.kt => CompletionHandler.common.kt} | 13 + ...eContext.kt => CoroutineContext.common.kt} | 12 +- .../experimental/CoroutineDispatcher.kt | 15 +- .../experimental/CoroutineExceptionHandler.kt | 56 +- .../{CommonDelay.kt => Debug.common.kt} | 5 +- .../coroutines/experimental/Deferred.kt | 17 +- .../kotlinx/coroutines/experimental/Delay.kt | 28 +- .../coroutines/experimental/Dispatched.kt | 5 +- ...mmonExceptions.kt => Exceptions.common.kt} | 4 +- .../kotlinx/coroutines/experimental/Job.kt | 340 ++--- .../coroutines/experimental/NonCancellable.kt | 26 +- .../{CommonDebug.kt => Runnable.common.kt} | 7 +- .../coroutines/experimental/Scheduled.kt | 27 +- .../coroutines/experimental/Unconfined.kt | 36 + .../kotlinx/coroutines/experimental/Yield.kt | 7 +- .../experimental/internal/Atomic.kt | 2 +- .../internal/LockFreeLinkedList.common.kt | 67 + .../coroutines/experimental/selects/Select.kt | 41 +- .../experimental/selects/SelectUnbiased.kt | 14 +- .../experimental/selects/WhileSelect.kt | 0 .../coroutines/experimental/sync/Mutex.kt | 36 +- .../TimeUnit.common.kt} | 9 +- ...ommonAsyncLazyTest.kt => AsyncLazyTest.kt} | 2 +- .../{CommonAsyncTest.kt => AsyncTest.kt} | 2 +- ...est.kt => AtomicCancellationCommonTest.kt} | 42 +- ...rredTest.kt => CompletableDeferredTest.kt} | 2 +- ...st.kt => CoroutineExceptionHandlerTest.kt} | 2 +- ...monCoroutinesTest.kt => CoroutinesTest.kt} | 2 +- .../{CommonJobTest.kt => JobTest.kt} | 2 +- ...monLaunchLazyTest.kt => LaunchLazyTest.kt} | 2 +- .../{CommonTestBase.kt => TestBase.common.kt} | 0 ...nWithContextTest.kt => WithContextTest.kt} | 2 +- ...OrNullTest.kt => WithTimeoutOrNullTest.kt} | 52 +- ...nWithTimeoutTest.kt => WithTimeoutTest.kt} | 56 +- .../selects/SelectBuilderImplTest.kt | 16 +- .../selects/SelectDeferredTest.kt | 21 +- .../experimental/selects/SelectJobTest.kt | 3 +- .../experimental/selects/SelectMutexTest.kt | 29 +- .../experimental/selects/SelectTimeoutTest.kt | 11 +- .../coroutines/experimental/sync/MutexTest.kt | 34 +- core/kotlinx-coroutines-core/README.md | 4 + .../coroutines/experimental/Annotations.kt | 29 + .../coroutines/experimental/Builders.kt | 179 +-- .../coroutines/experimental/CommonPool.kt | 1 + .../experimental/CompletionHandler.kt | 27 + .../experimental/CoroutineContext.kt | 26 +- .../CoroutineExceptionHandlerImpl.kt | 18 +- .../kotlinx/coroutines/experimental/Debug.kt | 2 +- .../experimental/DefaultExecutor.kt | 4 +- .../coroutines/experimental/EventLoop.kt | 2 +- .../coroutines/experimental/Exceptions.kt | 40 +- .../coroutines/experimental/Executors.kt | 22 +- .../kotlinx/coroutines/experimental/Future.kt | 46 + .../coroutines/experimental/Runnable.kt | 29 + .../experimental/channels/AbstractChannel.kt | 6 +- .../coroutines/experimental/channels/Actor.kt | 3 +- .../internal/LockFreeLinkedList.kt | 54 +- .../experimental/timeunit/TimeUnit.kt | 23 + .../{AsyncTest.kt => AsyncJvmTest.kt} | 2 +- .../experimental/AtomicCancellationTest.kt | 38 - ...CoroutinesTest.kt => CoroutinesJvmTest.kt} | 2 +- .../{JobTest.kt => JobStressTest.kt} | 2 +- .../coroutines/experimental/TestBase.kt | 3 - ...xtTest.kt => WithContextCommonPoolTest.kt} | 2 +- ...ullTest.kt => WithTimeoutOrNullJvmTest.kt} | 52 +- .../WithTimeoutOrNullThreadDispatchTest.kt | 1 - .../experimental/WithTimeoutTest.kt | 76 -- .../BroadcastChannelMultiReceiveStressTest.kt | 2 +- .../channels/BroadcastChannelSubStressTest.kt | 2 +- .../selects/SelectMutexStressTest.kt | 46 + .../experimental/sync/MutexStressTest.kt | 42 + .../experimental/AbstractContinuation.kt | 102 -- .../coroutines/experimental/Annotations.kt | 27 + .../experimental/CancellableContinuation.kt | 280 ----- .../experimental/CompletableDeferred.kt | 82 -- .../experimental/CompletionHandler.kt | 20 +- .../experimental/CoroutineContext.kt | 21 +- .../experimental/CoroutineDispatcher.kt | 89 -- .../experimental/CoroutineExceptionHandler.kt | 80 -- .../CoroutineExceptionHandlerImpl.kt | 7 +- .../kotlinx/coroutines/experimental/Debug.kt | 13 + .../coroutines/experimental/Deferred.kt | 181 --- .../kotlinx/coroutines/experimental/Delay.kt | 76 -- .../coroutines/experimental/Exceptions.kt | 29 +- .../coroutines/experimental/JSDispatcher.kt | 20 +- .../kotlinx/coroutines/experimental/Job.kt | 1113 ----------------- .../coroutines/experimental/NonCancellable.kt | 68 - .../coroutines/experimental/Runnable.kt | 35 + .../coroutines/experimental/Scheduled.kt | 125 -- .../kotlinx/coroutines/experimental/Yield.kt | 43 - .../experimental/internal/LinkedList.kt | 129 +- .../experimental/timeunit/TimeUnit.kt | 41 + .../coroutines/experimental/TestBase.kt | 4 +- 105 files changed, 1281 insertions(+), 3590 deletions(-) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt (95%) create mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.common.kt rename js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt => common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt (75%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt (88%) delete mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt delete mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCancellableContinuation.kt delete mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineDispatcher.kt delete mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDeferred.kt delete mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonJob.kt delete mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonNonCancellable.kt rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt (85%) rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/{CommonCompletionHandler.kt => CompletionHandler.common.kt} (65%) rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/{CommonCoroutineContext.kt => CoroutineContext.common.kt} (74%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt (89%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt (69%) rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/{CommonDelay.kt => Debug.common.kt} (87%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt (96%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt (84%) rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/{CommonExceptions.kt => Exceptions.common.kt} (90%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt (86%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt (73%) rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/{CommonDebug.kt => Runnable.common.kt} (80%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt (90%) create mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Unconfined.kt rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt (84%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt (99%) create mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt (90%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt (89%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt (100%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt (95%) rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/{CommonYield.kt => timeunit/TimeUnit.common.kt} (79%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonAsyncLazyTest.kt => AsyncLazyTest.kt} (99%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonAsyncTest.kt => AsyncTest.kt} (98%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonAtomicCancellationTest.kt => AtomicCancellationCommonTest.kt} (70%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonCompletableDeferredTest.kt => CompletableDeferredTest.kt} (99%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonCoroutineExceptionHandlerTest.kt => CoroutineExceptionHandlerTest.kt} (95%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonCoroutinesTest.kt => CoroutinesTest.kt} (99%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonJobTest.kt => JobTest.kt} (99%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonLaunchLazyTest.kt => LaunchLazyTest.kt} (98%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonTestBase.kt => TestBase.common.kt} (100%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonWithContextTest.kt => WithContextTest.kt} (99%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonWithTimeoutOrNullTest.kt => WithTimeoutOrNullTest.kt} (75%) rename common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/{CommonWithTimeoutTest.kt => WithTimeoutTest.kt} (70%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt (90%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt (88%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt (97%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt (68%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt (79%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt (79%) create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.kt create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandler.kt => core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerImpl.kt (56%) create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.kt create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.kt rename core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/{AsyncTest.kt => AsyncJvmTest.kt} (98%) rename core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/{CoroutinesTest.kt => CoroutinesJvmTest.kt} (97%) rename core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/{JobTest.kt => JobStressTest.kt} (96%) rename core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/{WithContextTest.kt => WithContextCommonPoolTest.kt} (96%) rename core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/{WithTimeoutOrNullTest.kt => WithTimeoutOrNullJvmTest.kt} (51%) delete mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexStressTest.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexStressTest.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt create mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferred.kt => js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt (57%) delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt rename common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonScheduled.kt => js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerImpl.kt (76%) delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt create mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt delete mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt create mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.kt diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt similarity index 95% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt index f747f021eb..67a7fc64e7 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt @@ -16,10 +16,9 @@ package kotlinx.coroutines.experimental -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.loop -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED +import kotlinx.atomicfu.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* private const val UNDECIDED = 0 private const val SUSPENDED = 1 diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.common.kt new file mode 100644 index 0000000000..a0ea9b3d6b --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.common.kt @@ -0,0 +1,29 @@ +/* + * 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. + */ + +// NOTE: We are defining them in a special internalAnnotations package because they would break +// user code that uses kotlinx.coroutines library otherwise, see https://youtrack.jetbrains.com/issue/KT-23727 +package kotlinx.coroutines.experimental.internalAnnotations + +@Target(AnnotationTarget.FILE) +internal expect annotation class JvmName(val name: String) + +@Target(AnnotationTarget.FILE) +internal expect annotation class JvmMultifileClass() + +internal expect annotation class JvmField() + +internal expect annotation class Volatile() diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt similarity index 75% rename from js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt index 896031ee67..5c6d1f20c1 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt @@ -14,15 +14,18 @@ * limitations under the License. */ -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +@file:JvmMultifileClass +@file:JvmName("BuildersKt") package kotlinx.coroutines.experimental +import kotlinx.coroutines.experimental.internalAnnotations.* import kotlinx.coroutines.experimental.intrinsics.* import kotlin.coroutines.experimental.* import kotlin.coroutines.experimental.intrinsics.* +// --------------- basic coroutine builders --------------- + /** * 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]. @@ -54,7 +57,7 @@ import kotlin.coroutines.experimental.intrinsics.* * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ -public actual fun launch( +public fun launch( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, @@ -69,6 +72,31 @@ public actual fun launch( coroutine.start(start, coroutine, block) return coroutine } +/** @suppress **Deprecated**: Binary compatibility */ +@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) +public fun launch( + context: CoroutineContext = DefaultDispatcher, + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + block: suspend CoroutineScope.() -> Unit +): Job = launch(context, start, parent, block = block) + +/** @suppress **Deprecated**: Binary compatibility */ +@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) +public fun launch( + context: CoroutineContext = DefaultDispatcher, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +): Job = + launch(context, start, block = block) + +/** + * @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter + */ +@Deprecated(message = "Use `start = CoroutineStart.XXX` parameter", + replaceWith = ReplaceWith("launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)")) +public fun launch(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> Unit): Job = + launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block = block) /** * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns @@ -86,7 +114,7 @@ public actual fun launch( * Other options can be specified via `start` parameter. See [CoroutineStart] for details. * A value of [CoroutineStart.LAZY] is not supported and produces [IllegalArgumentException]. */ -public actual suspend fun withContext( +public suspend fun withContext( context: CoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend () -> T @@ -111,17 +139,34 @@ public actual suspend fun withContext( val completion = RunCompletion( context = newContext, delegate = cont, - resumeMode = if (start == CoroutineStart.ATOMIC) MODE_ATOMIC_DEFAULT else MODE_CANCELLABLE) + resumeMode = if (start == CoroutineStart.ATOMIC) MODE_ATOMIC_DEFAULT else MODE_CANCELLABLE + ) completion.initParentJobInternal(newContext[Job]) // attach to job + @Suppress("DEPRECATION") start(block, completion) completion.getResult() } +/** @suppress **Deprecated**: Renamed to [withContext]. */ +@Deprecated(message = "Renamed to `withContext`", level=DeprecationLevel.WARNING, + replaceWith = ReplaceWith("withContext(context, start, block)")) +public suspend fun run( + context: CoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend () -> T +): T = + withContext(context, start, block) + +/** @suppress **Deprecated** */ +@Deprecated(message = "It is here for binary compatibility only", level=DeprecationLevel.HIDDEN) +public suspend fun run(context: CoroutineContext, block: suspend () -> T): T = + withContext(context, start = CoroutineStart.ATOMIC, block = block) + // --------------- implementation --------------- private open class StandaloneCoroutine( - private val parentContext: CoroutineContext, - active: Boolean + private val parentContext: CoroutineContext, + active: Boolean ) : AbstractCoroutine(parentContext, active) { override fun hasOnFinishingHandler(update: Any?) = update is CompletedExceptionally override fun onFinishingInternal(update: Any?) { @@ -131,8 +176,8 @@ private open class StandaloneCoroutine( } private class LazyStandaloneCoroutine( - parentContext: CoroutineContext, - private val block: suspend CoroutineScope.() -> Unit + parentContext: CoroutineContext, + private val block: suspend CoroutineScope.() -> Unit ) : StandaloneCoroutine(parentContext, active = false) { override fun onStart() { block.startCoroutineCancellable(this, this) @@ -144,12 +189,9 @@ private class RunContinuationDirect( continuation: Continuation ) : Continuation by continuation - @Suppress("UNCHECKED_CAST") private class RunCompletion( override val context: CoroutineContext, delegate: Continuation, resumeMode: Int ) : AbstractContinuation(delegate, resumeMode) - - diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt similarity index 88% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt index bba910fc6b..3b4ff8a363 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt @@ -14,16 +14,12 @@ * limitations under the License. */ -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - package kotlinx.coroutines.experimental -import kotlinx.coroutines.experimental.internal.LockFreeLinkedListNode -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn -import kotlin.coroutines.experimental.suspendCoroutine +import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* // --------------- cancellable continuations --------------- @@ -61,24 +57,24 @@ import kotlin.coroutines.experimental.suspendCoroutine * * ``` */ -public actual interface CancellableContinuation : Continuation, Job { +public interface CancellableContinuation : Continuation, Job { /** * Returns `true` when this continuation is active -- it has not completed or cancelled yet. */ - public actual override val isActive: Boolean + public override val isActive: Boolean /** * Returns `true` when this continuation has completed for any reason. A continuation * that was cancelled is also considered complete. */ - public actual override val isCompleted: Boolean + public override val isCompleted: Boolean /** * Returns `true` if this continuation was [cancelled][cancel]. * * It implies that [isActive] is `false` and [isCompleted] is `true`. */ - public actual override val isCancelled: Boolean + public override val isCancelled: Boolean /** * Tries to resume this continuation with a given value and returns non-null object token if it was successful, @@ -90,7 +86,7 @@ public actual interface CancellableContinuation : Continuation, Job { * * @suppress **This is unstable API and it is subject to change.** */ - public actual fun tryResume(value: T, idempotent: Any? = null): Any? + public fun tryResume(value: T, idempotent: Any? = null): Any? /** * Tries to resume this continuation with a given exception and returns non-null object token if it was successful, @@ -106,20 +102,20 @@ public actual interface CancellableContinuation : Continuation, Job { * * @suppress **This is unstable API and it is subject to change.** */ - public actual fun completeResume(token: Any) + public fun completeResume(token: Any) /** * Makes this continuation cancellable. Use it with `holdCancellability` optional parameter to * [suspendCancellableCoroutine] function. It throws [IllegalStateException] if invoked more than once. */ - public actual fun initCancellability() + public fun initCancellability() /** * Cancels this continuation with an optional cancellation [cause]. The result is `true` if this continuation was * cancelled as a result of this invocation and `false` otherwise. */ @Suppress("DEFAULT_VALUE_NOT_ALLOWED_IN_OVERRIDE") - public actual override fun cancel(cause: Throwable? = null): Boolean + public override fun cancel(cause: Throwable? = null): Boolean /** * Registers handler that is **synchronously** invoked once on completion of this continuation. @@ -135,7 +131,7 @@ public actual interface CancellableContinuation : Continuation, Job { * 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. */ - public actual override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle + public override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle /** * Resumes this continuation with a given [value] in the invoker thread without going though @@ -143,7 +139,7 @@ public actual interface CancellableContinuation : Continuation, Job { * This function is designed to be used only by the [CoroutineDispatcher] implementations themselves. * **It should not be used in general code**. */ - public actual fun CoroutineDispatcher.resumeUndispatched(value: T) + public fun CoroutineDispatcher.resumeUndispatched(value: T) /** * Resumes this continuation with a given [exception] in the invoker thread without going though @@ -151,7 +147,7 @@ public actual interface CancellableContinuation : Continuation, Job { * This function is designed to be used only by the [CoroutineDispatcher] implementations themselves. * **It should not be used in general code**. */ - public actual fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable) + public fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable) } /** @@ -163,7 +159,7 @@ public actual interface CancellableContinuation : Continuation, Job { * * See [suspendAtomicCancellableCoroutine] for suspending functions that need *atomic cancellation*. */ -public actual inline suspend fun suspendCancellableCoroutine( +public suspend inline fun suspendCancellableCoroutine( holdCancellability: Boolean = false, crossinline block: (CancellableContinuation) -> Unit ): T = @@ -172,7 +168,7 @@ public actual inline suspend fun suspendCancellableCoroutine( if (!holdCancellability) cancellable.initCancellability() block(cancellable) cancellable.getResult() -} + } /** * Suspends coroutine similar to [suspendCancellableCoroutine], but with *atomic cancellation*. @@ -182,7 +178,7 @@ public actual inline suspend fun suspendCancellableCoroutine( * continue to execute even after it was cancelled from the same thread in the case when the continuation * was already resumed and was posted for execution to the thread's queue. */ -public actual inline suspend fun suspendAtomicCancellableCoroutine( +public suspend inline fun suspendAtomicCancellableCoroutine( holdCancellability: Boolean = false, crossinline block: (CancellableContinuation) -> Unit ): T = @@ -198,15 +194,15 @@ public actual inline suspend fun suspendAtomicCancellableCoroutine( * @suppress **This is unstable API and it is subject to change.** */ public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNode): DisposableHandle = - invokeOnCompletion(handler = RemoveOnCancel(this, node)) + invokeOnCompletion(handler = RemoveOnCancel(this, node).asHandler) // --------------- implementation details --------------- private class RemoveOnCancel( cont: CancellableContinuation<*>, - val node: LockFreeLinkedListNode + @JvmField val node: LockFreeLinkedListNode ) : JobNode>(cont) { - override fun invoke(reason: Throwable?) { + override fun invoke(cause: Throwable?) { if (job.isCancelled) node.remove() } @@ -283,12 +279,16 @@ internal class CancellableContinuationImpl( override fun nameString(): String = "CancellableContinuation(${delegate.toDebugString()})" + + // todo: This workaround for KT-21968, should be removed in the future + public override fun cancel(cause: Throwable?): Boolean = + super.cancel(cause) } private class CompletedIdempotentResult( @JvmField val idempotentResume: Any?, @JvmField val result: Any?, - @JvmField val token: JobSupport.Incomplete + @JvmField val token: Incomplete ) { override fun toString(): String = "CompletedIdempotentResult[$result]" } diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt deleted file mode 100644 index 7522850a12..0000000000 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt +++ /dev/null @@ -1,35 +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.* - -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect fun launch( - context: CoroutineContext = DefaultDispatcher, - start: CoroutineStart = CoroutineStart.DEFAULT, - parent: Job? = null, - onCompletion: CompletionHandler? = null, - block: suspend CoroutineScope.() -> Unit -): Job - -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect suspend fun withContext( - context: CoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, - block: suspend () -> T -): T diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCancellableContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCancellableContinuation.kt deleted file mode 100644 index 663cc58d2b..0000000000 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCancellableContinuation.kt +++ /dev/null @@ -1,49 +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. - */ - -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - -package kotlinx.coroutines.experimental - -import kotlin.coroutines.experimental.Continuation - -public expect interface CancellableContinuation : Continuation { - public val isActive: Boolean - public val isCompleted: Boolean - public val isCancelled: Boolean - @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") - public fun tryResume(value: T, idempotent: Any? = null): Any? - public fun completeResume(token: Any) - public fun initCancellability() - @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") - public actual fun cancel(cause: Throwable? = null): Boolean - public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle - public fun CoroutineDispatcher.resumeUndispatched(value: T) - public fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable) -} - -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect suspend fun suspendCancellableCoroutine( - holdCancellability: Boolean = false, - block: (CancellableContinuation) -> Unit -): T - -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect suspend fun suspendAtomicCancellableCoroutine( - holdCancellability: Boolean = false, - block: (CancellableContinuation) -> Unit -): T diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineDispatcher.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineDispatcher.kt deleted file mode 100644 index e184307b12..0000000000 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineDispatcher.kt +++ /dev/null @@ -1,32 +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.* - -public expect abstract class CoroutineDispatcher constructor() : AbstractCoroutineContextElement, ContinuationInterceptor { - public open fun isDispatchNeeded(context: CoroutineContext): Boolean - public abstract fun dispatch(context: CoroutineContext, block: Runnable) - public override fun interceptContinuation(continuation: Continuation): Continuation -} - -public expect interface Runnable { - public fun run() -} - -@Suppress("PropertyName") -public expect val DefaultDispatcher: CoroutineDispatcher diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDeferred.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDeferred.kt deleted file mode 100644 index 3c6786c833..0000000000 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDeferred.kt +++ /dev/null @@ -1,35 +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.* - -public expect interface Deferred : Job { - public val isCompletedExceptionally: Boolean - public suspend fun await(): T - public fun getCompleted(): T - public fun getCompletionExceptionOrNull(): Throwable? -} - -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect fun async( - context: CoroutineContext = DefaultDispatcher, - start: CoroutineStart = CoroutineStart.DEFAULT, - parent: Job? = null, - onCompletion: CompletionHandler? = null, - block: suspend CoroutineScope.() -> T -): Deferred \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonJob.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonJob.kt deleted file mode 100644 index 5fb9b847e4..0000000000 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonJob.kt +++ /dev/null @@ -1,101 +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. - */ - -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - -package kotlinx.coroutines.experimental - -import kotlin.coroutines.experimental.CoroutineContext - -expect public interface Job : CoroutineContext.Element { - public companion object Key : CoroutineContext.Key - public val isActive: Boolean - public val isCompleted: Boolean - public val isCancelled: Boolean - public fun getCancellationException(): CancellationException - public fun start(): Boolean - @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") - public fun cancel(cause: Throwable? = null): Boolean - public val children: Sequence - @Deprecated(message = "Start child coroutine with 'parent' parameter", level = DeprecationLevel.WARNING) - public fun attachChild(child: Job): DisposableHandle - public suspend fun join() - @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") - public actual fun invokeOnCompletion( - onCancelling: Boolean = false, - invokeImmediately: Boolean = true, - handler: CompletionHandler): DisposableHandle -} - -@Suppress("FunctionName", "EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect fun Job(parent: Job? = null): Job - -public expect interface DisposableHandle { - public fun dispose() -} - -public expect val CoroutineContext.isActive: Boolean -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect fun CoroutineContext.cancel(cause: Throwable? = null): Boolean -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect fun CoroutineContext.cancelChildren(cause: Throwable? = null) - -public expect fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle -public expect suspend fun Job.cancelAndJoin() -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER", "EXTENSION_SHADOWED_BY_MEMBER") // See KT-21598 -public expect fun Job.cancelChildren(cause: Throwable? = null) -public expect suspend fun Job.joinChildren() - -public expect object NonDisposableHandle : DisposableHandle { - override fun dispose() -} - -internal expect open class JobSupport(active: Boolean) : Job { - public final override val key: CoroutineContext.Key<*> - public final override val isActive: Boolean - public final override val isCompleted: Boolean - public final override val isCancelled: Boolean - - public final override fun getCancellationException(): CancellationException - public final override fun start(): Boolean - public final override val children: Sequence - - public override fun cancel(cause: Throwable?): Boolean - - public final override fun attachChild(child: Job): DisposableHandle - public final override suspend fun join() - - // todo: non-final as a workaround for KT-21968, should be final in the future - public override fun invokeOnCompletion( - onCancelling: Boolean, - invokeImmediately: Boolean, - handler: CompletionHandler - ): DisposableHandle - - public val isCompletedExceptionally: Boolean - public fun getCompletionExceptionOrNull(): Throwable? - - internal fun initParentJobInternal(parent: Job?) - internal fun makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean - internal open fun hasOnFinishingHandler(update: Any?): Boolean - internal open fun onFinishingInternal(update: Any?) - internal open fun onCompletionInternal(state: Any?, mode: Int) - internal open fun onStartInternal() - internal open fun onCancellationInternal(exceptionally: CompletedExceptionally?) - internal open fun nameString(): String - internal open fun handleException(exception: Throwable) -} \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonNonCancellable.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonNonCancellable.kt deleted file mode 100644 index 756c06e1fd..0000000000 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonNonCancellable.kt +++ /dev/null @@ -1,31 +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 - -public expect object NonCancellable : Job { - override val isActive: Boolean - override val isCompleted: Boolean - override val isCancelled: Boolean - override fun start(): Boolean - suspend override fun join() - override fun getCancellationException(): CancellationException - override fun invokeOnCompletion(onCancelling: Boolean, invokeImmediately: Boolean, handler: CompletionHandler): DisposableHandle - override fun cancel(cause: Throwable?): Boolean - override val children: Sequence - @Suppress("OverridingDeprecatedMember") - override fun attachChild(child: Job): DisposableHandle -} diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt similarity index 85% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt index af61dd9080..d1d88949f1 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt @@ -14,9 +14,6 @@ * limitations under the License. */ -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.selects.* @@ -32,14 +29,14 @@ import kotlinx.coroutines.experimental.selects.* * 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 actual interface CompletableDeferred : Deferred { +public interface CompletableDeferred : Deferred { /** * Completes this deferred value with a given [value]. The result is `true` if this deferred was * completed as a result of this invocation and `false` otherwise (if it was already completed). * * Repeated invocations of this function have no effect and always produce `false`. */ - public actual fun complete(value: T): Boolean + public fun complete(value: T): Boolean /** * Completes this deferred value exceptionally with a given [exception]. The result is `true` if this deferred was @@ -47,7 +44,7 @@ public actual interface CompletableDeferred : Deferred { * * Repeated invocations of this function have no effect and always produce `false`. */ - public actual fun completeExceptionally(exception: Throwable): Boolean + public fun completeExceptionally(exception: Throwable): Boolean } /** @@ -55,7 +52,7 @@ public actual interface CompletableDeferred : Deferred { * It is optionally a child of a [parent] job. */ @Suppress("FunctionName") -public actual fun CompletableDeferred(parent: Job? = null): CompletableDeferred = CompletableDeferredImpl(parent) +public fun CompletableDeferred(parent: Job? = null): CompletableDeferred = CompletableDeferredImpl(parent) /** @suppress **Deprecated:** Binary compatibility only */ @Deprecated(message = "Binary compatibility only", level = DeprecationLevel.HIDDEN) @@ -66,7 +63,7 @@ public fun CompletableDeferred(): CompletableDeferred = CompletableDeferr * Creates an already _completed_ [CompletableDeferred] with a given [value]. */ @Suppress("FunctionName") -public actual fun CompletableDeferred(value: T): CompletableDeferred = CompletableDeferredImpl(null).apply { complete(value) } +public fun CompletableDeferred(value: T): CompletableDeferred = CompletableDeferredImpl(null).apply { complete(value) } /** * Concrete implementation of [CompletableDeferred]. diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt index 8984a28ccf..8e3276e24d 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt @@ -16,6 +16,8 @@ package kotlinx.coroutines.experimental +import kotlinx.coroutines.experimental.internalAnnotations.* + /** * Class for an internal state of a job that had completed exceptionally, including cancellation. * @@ -27,7 +29,7 @@ package kotlinx.coroutines.experimental * @suppress **This is unstable API and it is subject to change.** */ public open class CompletedExceptionally protected constructor( - public val cause: Throwable?, + @JvmField public val cause: Throwable?, allowNullCause: Boolean ) { /** diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCompletionHandler.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt similarity index 65% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCompletionHandler.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt index d0398619ae..dc2fd9ae31 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCompletionHandler.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt @@ -16,6 +16,8 @@ package kotlinx.coroutines.experimental +import kotlinx.coroutines.experimental.internal.* + /** * Handler for [Job.invokeOnCompletion]. * @@ -28,3 +30,14 @@ package kotlinx.coroutines.experimental * Implementations of `CompletionHandler` must be fast and _lock-free_. */ public typealias CompletionHandler = (cause: Throwable?) -> Unit + +// We want class that extends LockFreeLinkedListNode & CompletionHandler but we cannot do it on Kotlin/JS, +// so this expect class provides us with the corresponding abstraction in a platform-agnostic way. +internal expect abstract class CompletionHandlerNode() : LockFreeLinkedListNode { + val asHandler: CompletionHandler + abstract fun invoke(cause: Throwable?) +} + +// :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, +// because we play type tricks on Kotlin/JS and handler is not necessarily a function there +internal expect fun CompletionHandler.invokeIt(cause: Throwable?) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineContext.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.common.kt similarity index 74% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineContext.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.common.kt index c9aa2028da..a60cec0902 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineContext.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.common.kt @@ -18,10 +18,14 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* -public expect object Unconfined : CoroutineDispatcher { - override fun isDispatchNeeded(context: CoroutineContext): Boolean - override fun dispatch(context: CoroutineContext, block: Runnable) -} +@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") +public expect fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext + +@Suppress("PropertyName") +public expect val DefaultDispatcher: CoroutineDispatcher + +@Suppress("PropertyName") +internal expect val DefaultDelay: Delay internal expect inline fun withCoroutineContext(context: CoroutineContext, block: () -> T): T internal expect fun Continuation<*>.toDebugString(): String diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt similarity index 89% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt index feb4b0d29f..9b98ba44db 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt @@ -37,7 +37,7 @@ import kotlin.coroutines.experimental.* * * This class ensures that debugging facilities in [newCoroutineContext] function work properly. */ -public actual abstract class CoroutineDispatcher actual constructor() : +public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { /** * Returns `true` if execution shall be dispatched onto another thread. @@ -70,17 +70,17 @@ public actual abstract class CoroutineDispatcher actual constructor() : * parameter that allows one to optionally choose C#-style [CoroutineStart.UNDISPATCHED] behaviour * whenever it is needed for efficiency. */ - public actual open fun isDispatchNeeded(context: CoroutineContext): Boolean = true + public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true /** * Dispatches execution of a runnable [block] onto another thread in the given [context]. */ - public actual abstract fun dispatch(context: CoroutineContext, block: Runnable) + public abstract fun dispatch(context: CoroutineContext, block: Runnable) /** * Returns continuation that wraps the original [continuation], thus intercepting all resumptions. */ - public actual override fun interceptContinuation(continuation: Continuation): Continuation = + public override fun interceptContinuation(continuation: Continuation): Continuation = DispatchedContinuation(this, continuation) /** @@ -98,11 +98,6 @@ public actual abstract class CoroutineDispatcher actual constructor() : public operator fun plus(other: CoroutineDispatcher) = other // for nicer debugging - override fun toString(): String = - "${this::class.java.simpleName}@${Integer.toHexString(System.identityHashCode(this))}" + override fun toString(): String = "$classSimpleName@$hexAddress" } -/** - * A runnable task for [CoroutineDispatcher.dispatch]. - */ -public actual typealias Runnable = java.lang.Runnable diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt similarity index 69% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt index 2bb0cc7e2e..44e60fd056 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt @@ -16,9 +16,9 @@ package kotlinx.coroutines.experimental -import java.util.* -import kotlin.coroutines.experimental.AbstractCoroutineContextElement -import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.* + +internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) /** * Helper function for coroutine builder implementations to handle uncaught exception in coroutines. @@ -32,7 +32,7 @@ import kotlin.coroutines.experimental.CoroutineContext * * all instances of [CoroutineExceptionHandler] found via [ServiceLoader] are invoked; * * current thread's [Thread.uncaughtExceptionHandler] is invoked. */ -public actual fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { +public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { // if exception handling fails, make sure the original exception is not lost try { context[CoroutineExceptionHandler]?.let { @@ -43,23 +43,29 @@ public actual fun handleCoroutineException(context: CoroutineContext, exception: if (exception is CancellationException) return // try cancel job in the context context[Job]?.cancel(exception) - // use additional extension handlers - ServiceLoader.load(CoroutineExceptionHandler::class.java).forEach { handler -> - handler.handleException(context, exception) - } - // use thread's handler - val currentThread = Thread.currentThread() - currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) + // platform-specific + handleCoroutineExceptionImpl(context, exception) } catch (handlerException: Throwable) { // simply rethrow if handler threw the original exception if (handlerException === exception) throw exception // handler itself crashed for some other reason -- that is bad -- keep both throw RuntimeException("Exception while trying to handle coroutine exception", exception).apply { - addSuppressed(handlerException) + addSuppressedThrowable(handlerException) } } } +/** + * Creates new [CoroutineExceptionHandler] instance. + * @param handler a function which handles exception thrown by a coroutine + */ +@Suppress("FunctionName") +public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler = + object: AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { + override fun handleException(context: CoroutineContext, exception: Throwable) = + handler.invoke(context, exception) + } + /** * An optional element on the coroutine context to handle uncaught exceptions. * @@ -73,35 +79,15 @@ public actual fun handleCoroutineException(context: CoroutineContext, exception: * * See [handleCoroutineException]. */ -public actual interface CoroutineExceptionHandler : CoroutineContext.Element { +public interface CoroutineExceptionHandler : CoroutineContext.Element { /** * Key for [CoroutineExceptionHandler] instance in the coroutine context. */ - public actual companion object Key : CoroutineContext.Key { - /** - * Creates new [CoroutineExceptionHandler] instance. - * @param handler a function which handles exception thrown by a coroutine - * @suppress **Deprecated** - */ - @Deprecated("Replaced with top-level function", level = DeprecationLevel.HIDDEN) - public operator inline fun invoke(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler = - CoroutineExceptionHandler(handler) - } + public companion object Key : CoroutineContext.Key /** * Handles uncaught [exception] in the given [context]. It is invoked * if coroutine has an uncaught exception. See [handleCoroutineException]. */ - public actual fun handleException(context: CoroutineContext, exception: Throwable) + public fun handleException(context: CoroutineContext, exception: Throwable) } - -/** - * Creates new [CoroutineExceptionHandler] instance. - * @param handler a function which handles exception thrown by a coroutine - */ -@Suppress("FunctionName") -public actual inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler = - object: AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { - override fun handleException(context: CoroutineContext, exception: Throwable) = - handler.invoke(context, exception) - } \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDelay.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Debug.common.kt similarity index 87% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDelay.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Debug.common.kt index 51a8a886db..b6fb337fb7 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDelay.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Debug.common.kt @@ -16,6 +16,5 @@ package kotlinx.coroutines.experimental -public expect interface Delay - -public expect suspend fun delay(time: Int) +internal expect val Any.hexAddress: String +internal expect val Any.classSimpleName: String diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt similarity index 96% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt index ddc8e0ece7..3f15e8a580 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt @@ -14,9 +14,6 @@ * limitations under the License. */ -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.intrinsics.* @@ -81,14 +78,14 @@ import kotlin.coroutines.experimental.* * 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 actual interface Deferred : Job { +public interface Deferred : Job { /** * Returns `true` if computation of this deferred value has _completed exceptionally_ -- it had * either _failed_ with exception during computation or was [cancelled][cancel]. * * It implies that [isActive] is `false` and [isCompleted] is `true`. */ - public actual val isCompletedExceptionally: Boolean + public val isCompletedExceptionally: Boolean /** * Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete, @@ -101,7 +98,7 @@ public actual interface Deferred : Job { * This function can be used in [select] invocation with [onAwait] clause. * Use [isCompleted] to check for completion of this deferred value without waiting. */ - public actual suspend fun await(): T + public suspend fun await(): T /** * Clause for [select] expression of [await] suspending function that selects with the deferred value when it is @@ -118,7 +115,7 @@ public actual interface Deferred : Job { * This function is designed to be used from [invokeOnCompletion] handlers, when there is an absolute certainty that * the value is already complete. See also [getCompletionExceptionOrNull]. */ - public actual fun getCompleted(): T + public fun getCompleted(): T /** * Returns *completion exception* result if this deferred [completed exceptionally][isCompletedExceptionally], @@ -128,7 +125,7 @@ public actual interface Deferred : Job { * 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 actual fun getCompletionExceptionOrNull(): Throwable? + public fun getCompletionExceptionOrNull(): Throwable? /** * @suppress **Deprecated**: Use `isActive`. @@ -163,7 +160,7 @@ public actual interface Deferred : Job { * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). * @param block the coroutine code. */ -public actual fun async( +public fun async( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, @@ -209,7 +206,7 @@ public fun async(context: CoroutineContext, start: Boolean, block: suspend C * @suppress **Deprecated**: `defer` was renamed to `async`. */ @Deprecated(message = "`defer` was renamed to `async`", level = DeprecationLevel.WARNING, - replaceWith = ReplaceWith("async(context, block = block)")) + replaceWith = ReplaceWith("async(context, block = block)")) public fun defer(context: CoroutineContext, block: suspend CoroutineScope.() -> T): Deferred = async(context, block = block) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt similarity index 84% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt index 8d1e01ed33..25775d1206 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt @@ -16,12 +16,9 @@ package kotlinx.coroutines.experimental -import kotlinx.coroutines.experimental.selects.SelectBuilder -import kotlinx.coroutines.experimental.selects.select -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit -import kotlin.coroutines.experimental.ContinuationInterceptor -import kotlin.coroutines.experimental.CoroutineContext +import kotlinx.coroutines.experimental.selects.* +import kotlinx.coroutines.experimental.timeunit.* +import kotlin.coroutines.experimental.* /** * This dispatcher _feature_ is implemented by [CoroutineDispatcher] implementations that natively support @@ -30,7 +27,7 @@ import kotlin.coroutines.experimental.CoroutineContext * Implementation of this interface affects operation of * [delay][kotlinx.coroutines.experimental.delay] and [withTimeout] functions. */ -public actual interface Delay { +public interface Delay { /** * Delays coroutine for a given time without blocking a thread and resumes it after a specified time. * This suspending function is cancellable. @@ -68,7 +65,7 @@ public actual interface Delay { * This implementation uses a built-in single-threaded scheduled executor service. */ fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle = - DefaultExecutor.invokeOnTimeout(time, unit, block) + DefaultDelay.invokeOnTimeout(time, unit, block) } /** @@ -84,7 +81,7 @@ public actual interface Delay { * * @param time time in milliseconds. */ -public actual suspend fun delay(time: Int) = +public suspend fun delay(time: Int) = delay(time.toLong(), TimeUnit.MILLISECONDS) /** @@ -109,16 +106,5 @@ public suspend fun delay(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) { } } -/** - * An implementation of [DisposableHandle] that cancels the specified future on dispose. - * @suppress **This is unstable API and it is subject to change.** - */ -public class DisposableFutureHandle(private val future: Future<*>) : DisposableHandle { - override fun dispose() { - future.cancel(false) - } - override fun toString(): String = "DisposableFutureHandle[$future]" -} - /** Returns [Delay] implementation of the given context */ -internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultExecutor +internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Dispatched.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Dispatched.kt index ba29b44a2d..e81e5a920d 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Dispatched.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Dispatched.kt @@ -17,14 +17,15 @@ package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.* import kotlin.coroutines.experimental.* @Suppress("PrivatePropertyName") private val UNDEFINED = Symbol("UNDEFINED") internal class DispatchedContinuation( - val dispatcher: CoroutineDispatcher, - val continuation: Continuation + @JvmField val dispatcher: CoroutineDispatcher, + @JvmField val continuation: Continuation ) : Continuation by continuation, DispatchedTask { private var _state: Any? = UNDEFINED public override var resumeMode: Int = 0 diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonExceptions.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt similarity index 90% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonExceptions.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt index 437fc714b7..e2e67d38ab 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonExceptions.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt @@ -28,6 +28,6 @@ public expect class JobCancellationException( val job: Job } -public expect class TimeoutCancellationException public constructor(message: String) +internal expect class DispatchException(message: String, cause: Throwable) : RuntimeException -internal expect class DispatchException(message: String, cause: Throwable) : RuntimeException \ No newline at end of file +internal expect fun Throwable.addSuppressedThrowable(other: Throwable) \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt similarity index 86% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt index 6c03c077cd..e94e951b29 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +@file:JvmMultifileClass +@file:JvmName("JobKt") package kotlinx.coroutines.experimental import kotlinx.atomicfu.* import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.* import kotlinx.coroutines.experimental.intrinsics.* import kotlinx.coroutines.experimental.selects.* -import java.util.concurrent.* import kotlin.coroutines.experimental.* import kotlin.coroutines.experimental.intrinsics.* @@ -87,11 +87,11 @@ import kotlin.coroutines.experimental.intrinsics.* * 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 actual interface Job : CoroutineContext.Element { +public interface Job : CoroutineContext.Element { /** * Key for [Job] instance in the coroutine context. */ - public actual companion object Key : CoroutineContext.Key { + public companion object Key : CoroutineContext.Key { /** * Creates a new job object in _active_ state. * It is optionally a child of a [parent] job. @@ -117,20 +117,20 @@ public actual interface Job : CoroutineContext.Element { * The job that is waiting for its [children] to complete is still considered to be active if it * was not cancelled. */ - public actual val isActive: Boolean + 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] complete. */ - public actual val isCompleted: Boolean + 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 actual val isCancelled: Boolean + public val isCancelled: Boolean /** * Returns [CancellationException] that signals the completion of this job. This function is @@ -146,7 +146,7 @@ public actual interface Job : CoroutineContext.Element { * This function throws [IllegalStateException] when invoked on a job that has not * [completed][isCompleted] nor [cancelled][isCancelled] yet. */ - public actual fun getCancellationException(): CancellationException + public fun getCancellationException(): CancellationException /** * @suppress **Deprecated**: Renamed to [getCancellationException] @@ -162,7 +162,7 @@ public actual interface Job : CoroutineContext.Element { * The result `true` if this invocation actually started coroutine or `false` * if it was already started or completed. */ - public actual fun start(): Boolean + public fun start(): Boolean /** * Cancels this job with an optional cancellation [cause]. The result is `true` if this job was @@ -174,7 +174,7 @@ public actual interface Job : CoroutineContext.Element { * 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 actual fun cancel(cause: Throwable? = null): Boolean + public fun cancel(cause: Throwable? = null): Boolean // ------------ parent-child ------------ @@ -195,7 +195,7 @@ public actual interface Job : CoroutineContext.Element { * coroutine builders do not have uncaught exceptions by definition, since all their exceptions are * caught and are encapsulated in their result. */ - public actual val children: Sequence + public val children: Sequence /** * Attaches child job so that this job becomes its parent and @@ -217,7 +217,7 @@ public actual interface Job : CoroutineContext.Element { * @suppress This is an internal API. This method is too error prone for public API. */ @Deprecated(message = "Start child coroutine with 'parent' parameter", level = DeprecationLevel.WARNING) - public actual fun attachChild(child: Job): DisposableHandle + public fun attachChild(child: Job): DisposableHandle /** * Cancels all children jobs of this coroutine with the given [cause]. Unlike [cancel], @@ -250,7 +250,7 @@ public actual interface Job : CoroutineContext.Element { * * There is [cancelAndJoin] function that combines an invocation of [cancel] and `join`. */ - public actual suspend fun join() + public suspend fun join() /** * Clause for [select] expression of [join] suspending function that selects when the job is complete. @@ -324,7 +324,7 @@ public actual interface Job : CoroutineContext.Element { * when `false` then [NonDisposableHandle] is returned, but the [handler] is not invoked. * @param handler the handler. */ - public actual fun invokeOnCompletion( + public fun invokeOnCompletion( onCancelling: Boolean = false, invokeImmediately: Boolean = true, handler: CompletionHandler): DisposableHandle @@ -338,27 +338,10 @@ public actual interface Job : CoroutineContext.Element { */ @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) + "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() - } } /** @@ -366,26 +349,17 @@ public actual interface Job : CoroutineContext.Element { * It is optionally a child of a [parent] job. */ @Suppress("FunctionName") -public actual fun Job(parent: Job? = null): Job = JobImpl(parent) +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 actual interface DisposableHandle : Job.Registration { +public interface DisposableHandle { /** * Disposes the corresponding object, making it eligible for garbage collection. * Repeated invocation of this function has no effect. */ - public actual fun dispose() - - /** - * Unregisters completion handler. - * @suppress **Deprecated**: Replace with `dispose` - */ - @Deprecated(message = "Replace with `dispose`", - replaceWith = ReplaceWith("dispose()")) - public override fun unregister() = dispose() + public fun dispose() } // -------------------- Job extensions -------------------- @@ -402,7 +376,7 @@ public actual interface DisposableHandle : Job.Registration { @Deprecated(message = "Renamed to `disposeOnCompletion`", replaceWith = ReplaceWith("disposeOnCompletion(registration)")) public fun Job.unregisterOnCompletion(registration: DisposableHandle): DisposableHandle = - invokeOnCompletion(handler = DisposeOnCompletion(this, registration)) + invokeOnCompletion(handler = DisposeOnCompletion(this, registration).asHandler) /** * Disposes a specified [handle] when this job is complete. @@ -412,19 +386,8 @@ public fun Job.unregisterOnCompletion(registration: DisposableHandle): Disposabl * invokeOnCompletion { handle.dispose() } * ``` */ -public actual 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)) +public fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle = + invokeOnCompletion(handler = DisposeOnCompletion(this, handle).asHandler) /** * Cancels the job and suspends invoking coroutine until the cancelled job is complete. @@ -440,7 +403,7 @@ public fun Job.cancelFutureOnCompletion(future: Future<*>): DisposableHandle = * * This is a shortcut for the invocation of [cancel][Job.cancel] followed by [join][Job.join]. */ -public actual suspend fun Job.cancelAndJoin() { +public suspend fun Job.cancelAndJoin() { cancel() return join() } @@ -450,7 +413,7 @@ public actual suspend fun Job.cancelAndJoin() { * for all of them. Unlike [Job.cancel] on this job as a whole, the state of this job itself is not affected. */ @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // See KT-21598 -public actual fun Job.cancelChildren(cause: Throwable? = null) { +public fun Job.cancelChildren(cause: Throwable? = null) { children.forEach { it.cancel(cause) } } @@ -459,7 +422,7 @@ public actual fun Job.cancelChildren(cause: Throwable? = null) { * [Job.join] for all of them. Unlike [Job.join] on this job as a whole, it does not wait until * this job is complete. */ -public actual suspend fun Job.joinChildren() { +public suspend fun Job.joinChildren() { children.forEach { it.join() } } @@ -481,7 +444,7 @@ public actual suspend fun Job.joinChildren() { * The `coroutineContext.isActive` expression is a shortcut for `coroutineContext[Job]?.isActive == true`. * See [Job.isActive]. */ -public actual val CoroutineContext.isActive: Boolean +public val CoroutineContext.isActive: Boolean get() = this[Job]?.isActive == true /** @@ -489,7 +452,7 @@ public actual val CoroutineContext.isActive: Boolean * 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 actual fun CoroutineContext.cancel(cause: Throwable? = null): Boolean = +public fun CoroutineContext.cancel(cause: Throwable? = null): Boolean = this[Job]?.cancel(cause) ?: false /** @@ -497,8 +460,8 @@ public actual fun CoroutineContext.cancel(cause: Throwable? = null): Boolean = * It does not do anything if there is no job in the context or it has no children. * See [Job.cancelChildren] for details. */ -public actual fun CoroutineContext.cancelChildren(cause: Throwable? = null) { - this[Job]?.cancelChildren(cause) +public fun CoroutineContext.cancelChildren(cause: Throwable? = null) { + this[Job]?.cancelChildren(cause) } /** @@ -508,21 +471,12 @@ public actual fun CoroutineContext.cancelChildren(cause: Throwable? = null) { @Deprecated(message = "`join` is now a member function of `Job`") public suspend fun Job.join() = this.join() -/** - * No-op implementation of [Job.Registration]. - * @suppress: **Deprecated**: Replace with [NonDisposableHandle] - */ -@Deprecated(message = "Replace with `NonDisposableHandle`", - replaceWith = ReplaceWith("NonDisposableHandle")) -@Suppress("unused") -typealias EmptyRegistration = NonDisposableHandle - /** * No-op implementation of [DisposableHandle]. */ -public actual object NonDisposableHandle : DisposableHandle { +public object NonDisposableHandle : DisposableHandle { /** Does not do anything. */ - actual override fun dispose() {} + override fun dispose() {} /** Returns "NonDisposableHandle" string. */ override fun toString(): String = "NonDisposableHandle" @@ -540,8 +494,8 @@ public actual object NonDisposableHandle : DisposableHandle { * @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.** */ -internal actual open class JobSupport actual constructor(active: Boolean) : Job, SelectClause0 { - actual final override val key: CoroutineContext.Key<*> get() = Job +internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 { + final override val key: CoroutineContext.Key<*> get() = Job /* === Internal states === @@ -563,7 +517,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, === Transitions === New states Active states Inactive states - + +---------+ +---------+ } | EMPTY_N | --+-> | EMPTY_A | ----+ } Empty states +---------+ | +---------+ | } @@ -613,7 +567,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, * It shall be invoked at most once after construction after all other initialization. * @suppress **This is unstable API and it is subject to change.** */ - internal actual fun initParentJobInternal(parent: Job?) { + internal fun initParentJobInternal(parent: Job?) { check(parentHandle == null) if (parent == null) { parentHandle = NonDisposableHandle @@ -652,14 +606,14 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, } } - public actual final override val isActive: Boolean get() { + public final override val isActive: Boolean get() { val state = this.state return state is Incomplete && state.isActive } - public actual final override val isCompleted: Boolean get() = state !is Incomplete + public final override val isCompleted: Boolean get() = state !is Incomplete - public actual final override val isCancelled: Boolean get() { + public final override val isCancelled: Boolean get() { val state = this.state return state is Cancelled || (state is Finishing && state.cancelled != null) } @@ -695,7 +649,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, if (proposedUpdate !is CompletedExceptionally) return cancelled // not exception -- just use original cancelled val exception = proposedUpdate.exception if (cancelled.exception == exception) return cancelled // that is the cancelled we need already! - cancelled.cause?.let { exception.addSuppressed(it) } + cancelled.cause?.let { exception.addSuppressedThrowable(it) } return Cancelled(this, exception) } @@ -742,7 +696,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, try { node.invoke(cause) } catch (ex: Throwable) { - exception?.apply { addSuppressed(ex) } ?: run { + exception?.apply { addSuppressedThrowable(ex) } ?: run { exception = CompletionHandlerException("Exception in completion handler $node for $this", ex) } } @@ -756,7 +710,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, private fun notifyCancellation(list: NodeList, cause: Throwable?) = notifyHandlers>(list, cause) - public actual final override fun start(): Boolean { + public final override fun start(): Boolean { loopOnState { state -> when (startInternal(state)) { FALSE -> return false @@ -790,9 +744,9 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, * Override to provide the actual [start] action. * This function is invoked exactly once when non-active coroutine is [started][start]. */ - internal actual open fun onStartInternal() {} + internal open fun onStartInternal() {} - public actual final override fun getCancellationException(): CancellationException { + public final override fun getCancellationException(): CancellationException { val state = this.state return when { state is Finishing && state.cancelled != null -> @@ -838,7 +792,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, invokeOnCompletion(onCancelling = onCancelling_, invokeImmediately = true, handler = handler) // todo: non-final as a workaround for KT-21968, should be final in the future - public actual override fun invokeOnCompletion( + public override fun invokeOnCompletion( onCancelling: Boolean, invokeImmediately: Boolean, handler: CompletionHandler @@ -870,7 +824,9 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, } } else -> { // is complete - if (invokeImmediately) handler((state as? CompletedExceptionally)?.cause) + // :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, + // because we play type tricks on Kotlin/JS and handler is not necessarily a function there + if (invokeImmediately) handler.invokeIt((state as? CompletedExceptionally)?.cause) return NonDisposableHandle } } @@ -899,12 +855,12 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, // 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 + val list = state.nextNode // either our NodeList or somebody else won the race, updated state // just attempt converting it to list if state is still the same, then we'll continue lock-free loop _state.compareAndSet(state, list) } - public actual final override suspend fun join() { + public final override suspend fun join() { if (!joinInternal()) { // fast-path no wait return suspendCoroutineOrReturn { cont -> cont.context.checkCompletion() @@ -922,7 +878,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, } private suspend fun joinSuspend() = suspendCancellableCoroutine { cont -> - cont.disposeOnCompletion(invokeOnCompletion(handler = ResumeOnCompletion(this, cont))) + cont.disposeOnCompletion(invokeOnCompletion(handler = ResumeOnCompletion(this, cont).asHandler)) } public final override val onJoin: SelectClause0 @@ -943,7 +899,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, } if (startInternal(state) == 0) { // slow-path -- register waiter for completion - select.disposeOnSelect(invokeOnCompletion(handler = SelectJoinOnCompletion(this, select, block))) + select.disposeOnSelect(invokeOnCompletion(handler = SelectJoinOnCompletion(this, select, block).asHandler)) return } } @@ -976,7 +932,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, */ internal open val onCancelMode: Int get() = ON_CANCEL_MAKE_CANCELLING - public actual override fun cancel(cause: Throwable?): Boolean = when (onCancelMode) { + public override fun cancel(cause: Throwable?): Boolean = when (onCancelMode) { ON_CANCEL_MAKE_CANCELLED -> makeCancelled(cause) ON_CANCEL_MAKE_CANCELLING -> makeCancelling(cause) ON_CANCEL_MAKE_COMPLETING -> makeCompletingOnCancel(cause) @@ -1062,7 +1018,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, * @throws IllegalStateException if job is already complete or completing * @suppress **This is unstable API and it is subject to change.** */ - internal actual fun makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean = + internal fun makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean = when (makeCompletingInternal(proposedUpdate, mode)) { COMPLETING_COMPLETED -> true COMPLETING_WAITING_CHILDREN -> false @@ -1077,23 +1033,23 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, if (state is Finishing && state.completing) return COMPLETING_ALREADY_COMPLETING val child: Child? = firstChild(state) ?: // or else complete immediately w/o children - when { - state !is Finishing && hasOnFinishingHandler(proposedUpdate) -> null // unless it has onFinishing handler - updateState(state, proposedUpdate, mode) -> return COMPLETING_COMPLETED - else -> return@loopOnState - } + when { + state !is Finishing && hasOnFinishingHandler(proposedUpdate) -> null // unless it has onFinishing handler + updateState(state, proposedUpdate, mode) -> return COMPLETING_COMPLETED + else -> return@loopOnState + } val list = state.list ?: // must promote to list to correctly operate on child lists - when (state) { - is Empty -> { - promoteEmptyToNodeList(state) - return@loopOnState // retry - } - is JobNode<*> -> { - promoteSingleToNodeList(state) - return@loopOnState // retry - } - else -> error("Unexpected state with an empty list: $state") + when (state) { + is Empty -> { + promoteEmptyToNodeList(state) + return@loopOnState // retry + } + is JobNode<*> -> { + promoteSingleToNodeList(state) + return@loopOnState // retry } + else -> error("Unexpected state with an empty list: $state") + } // cancel all children in list on exceptional completion if (proposedUpdate is CompletedExceptionally) child?.cancelChildrenInternal(proposedUpdate.exception) @@ -1124,7 +1080,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, // return false when there is no more incomplete children to wait private tailrec fun tryWaitForChild(child: Child, proposedUpdate: Any?): Boolean { val handle = child.childJob.invokeOnCompletion(invokeImmediately = false, - handler = ChildCompletion(this, child, proposedUpdate)) + handler = ChildCompletion(this, child, proposedUpdate).asHandler) if (handle !== NonDisposableHandle) return true // child is not complete and we've started waiting for it val nextChild = child.nextChild() ?: return false return tryWaitForChild(nextChild, proposedUpdate) @@ -1148,16 +1104,16 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, private fun LockFreeLinkedListNode.nextChild(): Child? { var cur = this - while (cur.isRemoved) cur = cur.prev.unwrap() // rollback to prev non-removed (or list head) + while (cur.isRemoved) cur = cur.prevNode // rollback to prev non-removed (or list head) while (true) { - cur = cur.next.unwrap() + cur = cur.nextNode if (cur.isRemoved) continue if (cur is Child) return cur if (cur is NodeList) return null // checked all -- no more children } } - public actual final override val children: Sequence get() = buildSequence { + public final override val children: Sequence get() = buildSequence { val state = this@JobSupport.state when (state) { is Child -> yield(state.childJob) @@ -1168,8 +1124,8 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, } @Suppress("OverridingDeprecatedMember") - public actual final override fun attachChild(child: Job): DisposableHandle = - invokeOnCompletion(onCancelling = true, handler = Child(this, child)) + public final override fun attachChild(child: Job): DisposableHandle = + invokeOnCompletion(onCancelling = true, handler = Child(this, child).asHandler) @Suppress("OverridingDeprecatedMember") public final override fun cancelChildren(cause: Throwable?) { @@ -1181,7 +1137,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, * installed via [invokeOnCompletion]. * @suppress **This is unstable API and it is subject to change.** */ - internal actual open fun handleException(exception: Throwable) { + internal open fun handleException(exception: Throwable) { throw exception } @@ -1192,24 +1148,24 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, * null when it has completed normally. * @suppress **This is unstable API and it is subject to change.** */ - internal actual open fun onCancellationInternal(exceptionally: CompletedExceptionally?) {} + internal open fun onCancellationInternal(exceptionally: CompletedExceptionally?) {} /** * @suppress **This is unstable API and it is subject to change.** */ - internal actual open fun hasOnFinishingHandler(update: Any?) = false + internal open fun hasOnFinishingHandler(update: Any?) = false /** * @suppress **This is unstable API and it is subject to change.** */ - internal actual open fun onFinishingInternal(update: Any?) {} + internal open fun onFinishingInternal(update: Any?) {} /** * 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.** */ - internal actual open fun onCompletionInternal(state: Any?, mode: Int) {} + internal open fun onCompletionInternal(state: Any?, mode: Int) {} // for nicer debugging public override fun toString(): String = @@ -1218,7 +1174,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, /** * @suppress **This is unstable API and it is subject to change.** */ - internal actual open fun nameString(): String = this::class.java.simpleName + internal open fun nameString(): String = classSimpleName private fun stateString(): String { val state = this.state @@ -1234,14 +1190,6 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, } } - /** - * @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, @@ -1254,36 +1202,6 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, 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 TRUE - return RETRY - } - - override fun toString(): String = buildString { - append("List") - append(if (isActive) "{Active}" else "{New}") - append("[") - var first = true - this@NodeList.forEach> { node -> - if (first) first = false else append(", ") - append(node) - } - append("]") - } - } - /* * ================================================================================================= * This is ready-to-use implementation for Deferred interface. @@ -1292,9 +1210,9 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, * ================================================================================================= */ - public actual val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally + public val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally - public actual fun getCompletionExceptionOrNull(): Throwable? { + public fun getCompletionExceptionOrNull(): Throwable? { val state = this.state check(state !is Incomplete) { "This job has not completed yet" } return state.exceptionOrNull @@ -1360,7 +1278,7 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, } if (startInternal(state) == 0) { // slow-path -- register waiter for completion - select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(this, select, block))) + select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(this, select, block).asHandler)) return } } @@ -1369,8 +1287,8 @@ internal actual open class JobSupport actual constructor(active: Boolean) : Job, /** * @suppress **This is unstable API and it is subject to change.** */ - @Suppress("UNCHECKED_CAST") - internal fun selectAwaitCompletion(select: SelectInstance, block: suspend (T) -> R) { + @Suppress("UNCHECKED_CAST") + internal fun selectAwaitCompletion(select: SelectInstance, block: suspend (T) -> R) { val state = this.state // Note: await is non-atomic (can be cancelled while dispatched) if (state is CompletedExceptionally) @@ -1397,8 +1315,8 @@ 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 +private class Empty(override val isActive: Boolean) : Incomplete { + override val list: NodeList? get() = null override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}" } @@ -1409,28 +1327,59 @@ private class JobImpl(parent: Job? = null) : JobSupport(true) { // -------- invokeOnCompletion nodes -internal abstract class JobNode( +internal interface Incomplete { + val isActive: Boolean + val list: NodeList? // is null only for Empty and JobNode incomplete state objects +} + +internal abstract class JobNode actual constructor( @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) - abstract override fun invoke(reason: Throwable?) +) : CompletionHandlerNode(), DisposableHandle, Incomplete { + override val isActive: Boolean get() = true + override val list: NodeList? get() = null + override fun dispose() = (job as JobSupport).removeNode(this) +} + +internal class NodeList( + 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 TRUE + return RETRY + } + + override fun toString(): String = buildString { + append("List") + append(if (isActive) "{Active}" else "{New}") + append("[") + var first = true + this@NodeList.forEach> { node -> + if (first) first = false else append(", ") + append(node) + } + append("]") + } } private class InvokeOnCompletion( job: Job, private val handler: CompletionHandler ) : JobNode(job) { - override fun invoke(reason: Throwable?) = handler.invoke(reason) - override fun toString() = "InvokeOnCompletion[${handler::class.java.name}@${Integer.toHexString(System.identityHashCode(handler))}]" + override fun invoke(cause: Throwable?) = handler.invoke(cause) + override fun toString() = "InvokeOnCompletion[$classSimpleName@$hexAddress]" } private class ResumeOnCompletion( job: Job, private val continuation: Continuation ) : JobNode(job) { - override fun invoke(reason: Throwable?) = continuation.resume(Unit) + override fun invoke(cause: Throwable?) = continuation.resume(Unit) override fun toString() = "ResumeOnCompletion[$continuation]" } @@ -1438,28 +1387,16 @@ internal class DisposeOnCompletion( job: Job, private val handle: DisposableHandle ) : JobNode(job) { - override fun invoke(reason: Throwable?) = handle.dispose() + override fun invoke(cause: Throwable?) = handle.dispose() override fun toString(): String = "DisposeOnCompletion[$handle]" } -private class CancelFutureOnCompletion( - job: Job, - private val future: Future<*> -) : JobNode(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( job: JobSupport, private val select: SelectInstance, private val block: suspend () -> R ) : JobNode(job) { - override fun invoke(reason: Throwable?) { + override fun invoke(cause: Throwable?) { if (select.trySelect(null)) block.startCoroutineCancellable(select.completion) } @@ -1471,7 +1408,7 @@ private class SelectAwaitOnCompletion( private val select: SelectInstance, private val block: suspend (T) -> R ) : JobNode(job) { - override fun invoke(reason: Throwable?) { + override fun invoke(cause: Throwable?) { if (select.trySelect(null)) job.selectAwaitCompletion(select, block) } @@ -1492,17 +1429,17 @@ private class InvokeOnCancellation( ) : JobCancellationNode(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 invoke(cause: Throwable?) { + if (_invoked.compareAndSet(0, 1)) handler.invoke(cause) } - override fun toString() = "InvokeOnCancellation[${handler::class.java.name}@${Integer.toHexString(System.identityHashCode(handler))}]" + override fun toString() = "InvokeOnCancellation[$classSimpleName@$hexAddress]" } internal class Child( parent: JobSupport, @JvmField val childJob: Job ) : JobCancellationNode(parent) { - override fun invoke(reason: Throwable?) { + override fun invoke(cause: Throwable?) { // Always materialize the actual instance of parent's completion exception and cancel child with it childJob.cancel(job.getCancellationException()) } @@ -1514,8 +1451,7 @@ private class ChildCompletion( private val child: Child, private val proposedUpdate: Any? ) : JobNode(child.childJob) { - override fun invoke(reason: Throwable?) { + override fun invoke(cause: Throwable?) { parent.continueCompleting(child, proposedUpdate) } -} - +} \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt similarity index 73% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt index 51aa9cf4e2..4dc1b8abf1 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt @@ -17,8 +17,8 @@ package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.NonCancellable.isActive -import kotlinx.coroutines.experimental.selects.SelectClause0 -import kotlin.coroutines.experimental.AbstractCoroutineContextElement +import kotlinx.coroutines.experimental.selects.* +import kotlin.coroutines.experimental.* /** * A non-cancelable job that is always [active][isActive]. It is designed for [withContext] function @@ -31,21 +31,21 @@ import kotlin.coroutines.experimental.AbstractCoroutineContextElement * } * ``` */ -public actual object NonCancellable : AbstractCoroutineContextElement(Job), Job { +public object NonCancellable : AbstractCoroutineContextElement(Job), Job { /** Always returns `true`. */ - actual override val isActive: Boolean get() = true + override val isActive: Boolean get() = true /** Always returns `false`. */ - actual override val isCompleted: Boolean get() = false + override val isCompleted: Boolean get() = false /** Always returns `false`. */ - actual override val isCancelled: Boolean get() = false + override val isCancelled: Boolean get() = false /** Always returns `false`. */ - actual override fun start(): Boolean = false + override fun start(): Boolean = false /** Always throws [UnsupportedOperationException]. */ - actual suspend override fun join() { + override suspend fun join() { throw UnsupportedOperationException("This job is always active") } @@ -53,7 +53,7 @@ public actual object NonCancellable : AbstractCoroutineContextElement(Job), Job get() = throw UnsupportedOperationException("This job is always active") /** Always throws [IllegalStateException]. */ - actual override fun getCancellationException(): CancellationException = throw IllegalStateException("This job is always active") + override fun getCancellationException(): CancellationException = throw IllegalStateException("This job is always active") /** Always returns [NonDisposableHandle]. */ @Suppress("OverridingDeprecatedMember") @@ -71,19 +71,19 @@ public actual object NonCancellable : AbstractCoroutineContextElement(Job), Job NonDisposableHandle /** Always returns [NonDisposableHandle]. */ - actual override fun invokeOnCompletion(onCancelling: Boolean, invokeImmediately: Boolean, handler: CompletionHandler): DisposableHandle = + override fun invokeOnCompletion(onCancelling: Boolean, invokeImmediately: Boolean, handler: CompletionHandler): DisposableHandle = NonDisposableHandle /** Always returns `false`. */ - actual override fun cancel(cause: Throwable?): Boolean = false + override fun cancel(cause: Throwable?): Boolean = false /** Always returns [emptySequence]. */ - actual override val children: Sequence + override val children: Sequence get() = emptySequence() /** Always returns [NonDisposableHandle] and does not do anything. */ @Suppress("OverridingDeprecatedMember") - actual override fun attachChild(child: Job): DisposableHandle = NonDisposableHandle + override fun attachChild(child: Job): DisposableHandle = NonDisposableHandle /** Does not do anything. */ @Suppress("OverridingDeprecatedMember") diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDebug.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.common.kt similarity index 80% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDebug.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.common.kt index 3143a1f4c1..415294696a 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonDebug.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.common.kt @@ -16,4 +16,9 @@ package kotlinx.coroutines.experimental -internal expect val Any.classSimpleName: String \ No newline at end of file +public expect interface Runnable { + public fun run() +} + +@Suppress("FunctionName") +public expect inline fun Runnable(crossinline block: () -> Unit): Runnable \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt similarity index 90% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt index b881d983bb..93a522e789 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt @@ -16,9 +16,9 @@ package kotlinx.coroutines.experimental +import kotlinx.coroutines.experimental.internalAnnotations.* import kotlinx.coroutines.experimental.intrinsics.* -import kotlinx.coroutines.experimental.selects.* -import java.util.concurrent.* +import kotlinx.coroutines.experimental.timeunit.* import kotlin.coroutines.experimental.* import kotlin.coroutines.experimental.intrinsics.* @@ -39,7 +39,7 @@ import kotlin.coroutines.experimental.intrinsics.* * * @param time timeout time in milliseconds. */ -public actual suspend fun withTimeout(time: Int, block: suspend CoroutineScope.() -> T): T = +public suspend fun withTimeout(time: Int, block: suspend CoroutineScope.() -> T): T = withTimeout(time.toLong(), TimeUnit.MILLISECONDS, block) /** @@ -129,7 +129,7 @@ private open class TimeoutCoroutine( * * @param time timeout time in milliseconds. */ -public actual suspend fun withTimeoutOrNull(time: Int, block: suspend CoroutineScope.() -> T): T? = +public suspend fun withTimeoutOrNull(time: Int, block: suspend CoroutineScope.() -> T): T? = withTimeoutOrNull(time.toLong(), TimeUnit.MILLISECONDS, block) /** @@ -182,3 +182,22 @@ private class TimeoutOrNullCoroutine( } } +/** + * This exception is thrown by [withTimeout] to indicate timeout. + */ +public class TimeoutCancellationException 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) +} + +@Suppress("FunctionName") +internal fun TimeoutCancellationException( + time: Long, + unit: TimeUnit, + coroutine: Job +) : TimeoutCancellationException = TimeoutCancellationException("Timed out waiting for $time $unit", coroutine) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Unconfined.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Unconfined.kt new file mode 100644 index 0000000000..bb7397e403 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Unconfined.kt @@ -0,0 +1,36 @@ +/* + * 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.* + +/** + * A coroutine dispatcher that is not confined to any specific thread. + * It executes initial continuation of the coroutine _right here_ in the current call-frame + * and let the coroutine resume in whatever thread that is used by the corresponding suspending function, without + * mandating any specific threading policy. + * + * Note, that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption, + * but still want to execute it in the current call-frame until its first suspension, then you can use + * an optional [CoroutineStart] parameter in coroutine builders like [launch] and [async] setting it to the + * the value of [CoroutineStart.UNDISPATCHED]. + */ +public object Unconfined : CoroutineDispatcher() { + override fun isDispatchNeeded(context: CoroutineContext): Boolean = false + override fun dispatch(context: CoroutineContext, block: Runnable) { throw UnsupportedOperationException() } + override fun toString(): String = "Unconfined" +} diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt similarity index 84% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt index 833a728d66..2a537601ce 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt @@ -16,9 +16,8 @@ package kotlinx.coroutines.experimental -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED -import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* /** * Yields a thread (or thread pool) of the current coroutine dispatcher to other coroutines to run. @@ -28,7 +27,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn * If the [Job] of the current coroutine is cancelled or completed when this suspending function is invoked or while * this function is waiting for dispatching, it resumes with [CancellationException]. */ -public actual suspend fun yield(): Unit = suspendCoroutineOrReturn sc@ { cont -> +public suspend fun yield(): Unit = suspendCoroutineOrReturn sc@ { cont -> val context = cont.context context.checkCompletion() if (cont !is DispatchedContinuation) return@sc Unit diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt similarity index 99% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt index 1eaa6e6aa0..ce19e05c1c 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt @@ -41,7 +41,7 @@ private val NO_DECISION: Any = Symbol("NO_DECISION") * by Timothy L. Harris, Keir Fraser and Ian A. Pratt. * * Note: parts of atomic operation must be globally ordered. Otherwise, this implementation will produce - * [StackOverflowError]. + * `StackOverflowError`. * * @suppress **This is unstable API and it is subject to change.** */ diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt new file mode 100644 index 0000000000..9cc3e141c9 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt @@ -0,0 +1,67 @@ +/* + * 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.internal + +import kotlin.jvm.* + +/** @suppress **This is unstable API and it is subject to change.** */ +public expect open class LockFreeLinkedListNode() { + public val isRemoved: Boolean + public val nextNode: LockFreeLinkedListNode + public val prevNode: LockFreeLinkedListNode + public fun addLast(node: LockFreeLinkedListNode) + public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean + public inline fun addLastIf(node: LockFreeLinkedListNode, crossinline condition: () -> Boolean): Boolean + public inline fun addLastIfPrev( + node: LockFreeLinkedListNode, + predicate: (LockFreeLinkedListNode) -> Boolean + ): Boolean + + public inline fun addLastIfPrevAndIf( + node: LockFreeLinkedListNode, + predicate: (LockFreeLinkedListNode) -> Boolean, // prev node predicate + crossinline condition: () -> Boolean // atomically checked condition + ): Boolean + + public open fun remove(): Boolean + public fun removeFirstOrNull(): LockFreeLinkedListNode? + public inline fun removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? +} + +/** @suppress **This is unstable API and it is subject to change.** */ +public expect open class LockFreeLinkedListHead() : LockFreeLinkedListNode { + public val isEmpty: Boolean + public inline fun forEach(block: (T) -> Unit) + public final override fun remove(): Nothing +} + +/** @suppress **This is unstable API and it is subject to change.** */ +public expect open class AddLastDesc( + queue: LockFreeLinkedListNode, + node: T +) : AbstractAtomicDesc { + val queue: LockFreeLinkedListNode + val node: T + protected override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? +} + +/** @suppress **This is unstable API and it is subject to change.** */ +public expect abstract class AbstractAtomicDesc : AtomicDesc { + protected abstract fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? + final override fun prepare(op: AtomicOp<*>): Any? + final override fun complete(op: AtomicOp<*>, failure: Any?) +} diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt similarity index 90% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt index 8c6b208a57..85ba53c250 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt @@ -18,11 +18,10 @@ package kotlinx.coroutines.experimental.selects import kotlinx.atomicfu.* import kotlinx.coroutines.experimental.* -import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.* import kotlinx.coroutines.experimental.intrinsics.* -import kotlinx.coroutines.experimental.sync.* -import java.util.concurrent.* +import kotlinx.coroutines.experimental.timeunit.* import kotlin.coroutines.experimental.* import kotlin.coroutines.experimental.intrinsics.* @@ -58,30 +57,6 @@ public interface SelectBuilder { * @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 Deferred.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 SendChannel.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 ReceiveChannel.onReceive(block: suspend (E) -> R) { onReceive(block) } - - /** @suppress **Deprecated: for binary compatibility only **/ - @Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN) - public fun ReceiveChannel.onReceiveOrNull(block: suspend (E?) -> R) { onReceiveOrNull(block) } } /** @@ -222,7 +197,8 @@ private val RESUMED: Any = Symbol("RESUMED") @PublishedApi internal class SelectBuilderImpl( private val delegate: Continuation -) : LockFreeLinkedListHead(), SelectBuilder, SelectInstance, Continuation { +) : LockFreeLinkedListHead(), SelectBuilder, + SelectInstance, Continuation { // selection state is "this" (list of nodes) initially and is replaced by idempotent marker (or null) when selected private val _state = atomic(this) @@ -258,7 +234,9 @@ internal class SelectBuilderImpl( _result.loop { result -> when { result === UNDECIDED -> if (_result.compareAndSet(UNDECIDED, value())) return - result === COROUTINE_SUSPENDED -> if (_result.compareAndSet(COROUTINE_SUSPENDED, RESUMED)) { + result === COROUTINE_SUSPENDED -> if (_result.compareAndSet(COROUTINE_SUSPENDED, + RESUMED + )) { block() return } @@ -305,7 +283,8 @@ internal class SelectBuilderImpl( private fun initCancellability() { val parent = context[Job] ?: return - val newRegistration = parent.invokeOnCompletion(onCancelling = true, handler = SelectOnCancellation(parent)) + val newRegistration = parent.invokeOnCompletion( + onCancelling = true, handler = SelectOnCancellation(parent).asHandler) parentHandle = newRegistration // now check our state _after_ registering if (isSelected) newRegistration.dispose() @@ -313,7 +292,7 @@ internal class SelectBuilderImpl( private inner class SelectOnCancellation(job: Job) : JobCancellationNode(job) { // Note: may be invoked multiple times, but only the first trySelect succeeds anyway - override fun invoke(reason: Throwable?) { + override fun invoke(cause: Throwable?) { if (trySelect(null)) resumeSelectCancellableWithException(job.getCancellationException()) } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt similarity index 89% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt index cf79b473d9..59bfb5d873 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt @@ -16,10 +16,9 @@ package kotlinx.coroutines.experimental.selects -import java.util.* -import java.util.concurrent.TimeUnit -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn +import kotlinx.coroutines.experimental.timeunit.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* /** * Waits for the result of multiple suspending functions simultaneously like [select], but in an _unbiased_ @@ -31,7 +30,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn * * See [select] function description for all the other details. */ -public inline suspend fun selectUnbiased(crossinline builder: SelectBuilder.() -> Unit): R = +public suspend inline fun selectUnbiased(crossinline builder: SelectBuilder.() -> Unit): R = suspendCoroutineOrReturn { cont -> val scope = UnbiasedSelectBuilderImpl(cont) try { @@ -44,7 +43,8 @@ public inline suspend fun selectUnbiased(crossinline builder: SelectBuilder< @PublishedApi -internal class UnbiasedSelectBuilderImpl(cont: Continuation) : SelectBuilder { +internal class UnbiasedSelectBuilderImpl(cont: Continuation) : + SelectBuilder { val instance = SelectBuilderImpl(cont) val clauses = arrayListOf<() -> Unit>() @@ -55,7 +55,7 @@ internal class UnbiasedSelectBuilderImpl(cont: Continuation) : SelectBu internal fun initSelectResult(): Any? { if (!instance.isSelected) { try { - Collections.shuffle(clauses) + clauses.shuffle() clauses.forEach { it.invoke() } } catch (e: Throwable) { instance.handleBuilderException(e) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/WhileSelect.kt diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt similarity index 95% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt index 3130085ff5..98070597fb 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt @@ -16,16 +16,13 @@ package kotlinx.coroutines.experimental.sync -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.loop +import kotlinx.atomicfu.* import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.internal.* -import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched -import kotlinx.coroutines.experimental.selects.ALREADY_SELECTED -import kotlinx.coroutines.experimental.selects.SelectClause2 -import kotlinx.coroutines.experimental.selects.SelectInstance -import kotlinx.coroutines.experimental.selects.select -import kotlin.coroutines.experimental.startCoroutine +import kotlinx.coroutines.experimental.intrinsics.* +import kotlinx.coroutines.experimental.selects.* +import kotlin.coroutines.experimental.* +import kotlinx.coroutines.experimental.internalAnnotations.* /** * Mutual exclusion for coroutines. @@ -35,19 +32,6 @@ import kotlin.coroutines.experimental.startCoroutine * the lock still suspends the invoker. */ public interface Mutex { - /** - * Factory for [Mutex] instances. - * @suppress **Deprecated** - */ - public companion object Factory { - /** - * Creates new [Mutex] instance. - * @suppress **Deprecated** - */ - @Deprecated("Replaced with top-level function", level = DeprecationLevel.HIDDEN) - public operator fun invoke(locked: Boolean = false): Mutex = Mutex(locked) - } - /** * Returns `true` when this mutex is locked. */ @@ -113,7 +97,9 @@ public interface Mutex { * * @param locked initial state of the mutex. */ -public fun Mutex(locked: Boolean = false): Mutex = MutexImpl(locked) +@Suppress("FunctionName") +public fun Mutex(locked: Boolean = false): Mutex = + MutexImpl(locked) /** * Executes the given [action] under this mutex's lock. @@ -122,7 +108,7 @@ public fun Mutex(locked: Boolean = false): Mutex = MutexImpl(locked) * * @return the return value of the action. */ -public inline suspend fun Mutex.withLock(owner: Any? = null, action: () -> T): T { +public suspend inline fun Mutex.withLock(owner: Any? = null, action: () -> T): T { lock(owner) try { return action() @@ -200,7 +186,9 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 { when (state) { is Empty -> { if (state.locked !== UNLOCKED) return false - val update = if (owner == null) EmptyLocked else Empty(owner) + val update = if (owner == null) EmptyLocked else Empty( + owner + ) if (_state.compareAndSet(state, update)) return true } is LockedQueue -> { diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonYield.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.common.kt similarity index 79% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonYield.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.common.kt index e461c47f53..d41e2d2e56 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonYield.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.common.kt @@ -14,6 +14,11 @@ * limitations under the License. */ -package kotlinx.coroutines.experimental +package kotlinx.coroutines.experimental.timeunit -public expect suspend fun yield() \ No newline at end of file +public expect enum class TimeUnit { + MILLISECONDS, + SECONDS; + + public fun toMillis(time: Long): Long +} \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncLazyTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AsyncLazyTest.kt similarity index 99% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncLazyTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AsyncLazyTest.kt index 32e100ecdf..af3115cc0b 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncLazyTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AsyncLazyTest.kt @@ -21,7 +21,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonAsyncLazyTest : TestBase() { +class AsyncLazyTest : TestBase() { @Test fun testSimple() = runTest { expect(1) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt similarity index 98% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt index 28349bee08..2eac23cc36 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAsyncTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt @@ -21,7 +21,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonAsyncTest : TestBase() { +class AsyncTest : TestBase() { @Test fun testSimple() = runTest { expect(1) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAtomicCancellationTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationCommonTest.kt similarity index 70% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAtomicCancellationTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationCommonTest.kt index e1008c6235..fe69a8a3a9 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonAtomicCancellationTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationCommonTest.kt @@ -16,10 +16,12 @@ package kotlinx.coroutines.experimental +import kotlinx.coroutines.experimental.selects.* +import kotlinx.coroutines.experimental.sync.* import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonAtomicCancellationTest : TestBase() { +class AtomicCancellationCommonTest : TestBase() { @Test fun testCancellableLaunch() = runTest { expect(1) @@ -96,4 +98,42 @@ class CommonAtomicCancellationTest : TestBase() { yield() // to jobToJoin & canceller expect(6) } + + @Test + fun testLockAtomicCancel() = runTest { + expect(1) + val mutex = Mutex(true) // locked mutex + val job = launch(coroutineContext, start = CoroutineStart.UNDISPATCHED) { + expect(2) + mutex.lock() // suspends + expect(4) // should execute despite cancellation + } + expect(3) + mutex.unlock() // unlock mutex first + job.cancel() // cancel the job next + yield() // now yield + finish(5) + } + + @Test + fun testSelectLockAtomicCancel() = runTest { + expect(1) + val mutex = Mutex(true) // locked mutex + val job = launch(coroutineContext, start = CoroutineStart.UNDISPATCHED) { + expect(2) + val result = select { // suspends + mutex.onLock { + expect(4) + "OK" + } + } + assertEquals("OK", result) + expect(5) // should execute despite cancellation + } + expect(3) + mutex.unlock() // unlock mutex first + job.cancel() // cancel the job next + yield() // now yield + finish(6) + } } \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferredTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt similarity index 99% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferredTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt index 2e371d57a6..d7ea3ac311 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferredTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CompletableDeferredTest.kt @@ -21,7 +21,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonCompletableDeferredTest : TestBase() { +class CompletableDeferredTest : TestBase() { @Test fun testFresh() { val c = CompletableDeferred() diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandlerTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerTest.kt similarity index 95% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandlerTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerTest.kt index 4a6975362e..becd2c7fe6 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandlerTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerTest.kt @@ -19,7 +19,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonCoroutineExceptionHandlerTest : TestBase() { +class CoroutineExceptionHandlerTest : TestBase() { @Test fun testCoroutineExceptionHandlerCreator() = runTest { expect(1) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt similarity index 99% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt index 749475b6da..5ebd129534 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt @@ -21,7 +21,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonCoroutinesTest : TestBase() { +class CoroutinesTest : TestBase() { @Test fun testSimple() = runTest { expect(1) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonJobTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt similarity index 99% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonJobTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt index 3ff6f7e2e7..7ecbfdf9b0 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonJobTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt @@ -19,7 +19,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonJobTest : TestBase() { +class JobTest : TestBase() { @Test fun testState() { val job = Job() diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonLaunchLazyTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/LaunchLazyTest.kt similarity index 98% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonLaunchLazyTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/LaunchLazyTest.kt index 42f4fb87c4..bb2a845436 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonLaunchLazyTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/LaunchLazyTest.kt @@ -19,7 +19,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonLaunchLazyTest : TestBase() { +class LaunchLazyTest : TestBase() { @Test fun testLaunchAndYieldJoin() = runTest { expect(1) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonTestBase.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonTestBase.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithContextTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt similarity index 99% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithContextTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt index dde48b5f7f..2679ad58a1 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithContextTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt @@ -22,7 +22,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonWithContextTest : TestBase() { +class WithContextTest : TestBase() { @Test fun testSameContextNoSuspend() = runTest { expect(1) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutOrNullTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt similarity index 75% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutOrNullTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt index 891c9ef1ef..69b9bd4464 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutOrNullTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt @@ -22,7 +22,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonWithTimeoutOrNullTest : TestBase() { +class WithTimeoutOrNullTest : TestBase() { /** * Tests a case of no timeout and no suspension inside. */ @@ -142,4 +142,54 @@ class CommonWithTimeoutOrNullTest : TestBase() { override fun hashCode(): Int = error("Should not be called") override fun toString(): String = error("Should not be called") } + + @Test + fun testNullOnTimeout() = runTest { + expect(1) + val result = withTimeoutOrNull(100) { + expect(2) + delay(1000) + expectUnreached() + "OK" + } + assertEquals(null, result) + finish(3) + } + + @Test + fun testSuppressExceptionWithResult() = runTest { + expect(1) + val result = withTimeoutOrNull(100) { + expect(2) + try { + delay(1000) + } catch (e: CancellationException) { + expect(3) + } + "OK" + } + assertEquals(null, result) + finish(4) + } + + @Test + fun testSuppressExceptionWithAnotherException() = runTest( + expected = { it is TestException } + ) { + expect(1) + val result = withTimeoutOrNull(100) { + expect(2) + try { + delay(1000) + } catch (e: CancellationException) { + finish(3) + throw TestException() + } + expectUnreached() + "OK" + } + expectUnreached() + } + + private class TestException : Exception() } \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt similarity index 70% rename from common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt index 95894389c5..1aa236846e 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithTimeoutTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt @@ -22,7 +22,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CommonWithTimeoutTest : TestBase() { +class WithTimeoutTest : TestBase() { /** * Tests a case of no timeout and no suspension inside. */ @@ -125,5 +125,59 @@ class CommonWithTimeoutTest : TestBase() { override fun hashCode(): Int = error("Should not be called") override fun toString(): String = error("Should not be called") } + + @Test + fun testExceptionOnTimeout() = runTest { + expect(1) + try { + withTimeout(100) { + expect(2) + delay(1000) + expectUnreached() + "OK" + } + } catch (e: CancellationException) { + assertEquals("Timed out waiting for 100 MILLISECONDS", e.message) + finish(3) + } + } + + @Test + fun testSuppressExceptionWithResult() = runTest( + expected = { it is CancellationException } + ) { + expect(1) + val result = withTimeout(100) { + expect(2) + try { + delay(1000) + } catch (e: CancellationException) { + finish(3) + } + "OK" + } + expectUnreached() + } + + @Test + fun testSuppressExceptionWithAnotherException() = runTest( + expected = { it is TestException } + ) { + expect(1) + withTimeout(100) { + expect(2) + try { + delay(1000) + } catch (e: CancellationException) { + finish(3) + throw TestException() + } + expectUnreached() + "OK" + } + expectUnreached() + } + + private class TestException : Exception() } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt similarity index 90% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt index 39e289dc91..299156ad56 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBuilderImplTest.kt @@ -16,11 +16,9 @@ package kotlinx.coroutines.experimental.selects -import org.junit.Test -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.coroutines.experimental.EmptyCoroutineContext -import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* +import kotlin.test.* class SelectBuilderImplTest { @Test @@ -34,7 +32,7 @@ class SelectBuilderImplTest { } override fun resumeWithException(exception: Throwable) { error("Should not happen") } } - val c = SelectBuilderImpl(delegate) + val c = SelectBuilderImpl(delegate) // still running builder check(!c.isSelected) check(c.trySelect("SELECT")) @@ -60,7 +58,7 @@ class SelectBuilderImplTest { } override fun resumeWithException(exception: Throwable) { error("Should not happen") } } - val c = SelectBuilderImpl(delegate) + val c = SelectBuilderImpl(delegate) check(c.getResult() === COROUTINE_SUSPENDED) // suspend first check(!c.isSelected) check(c.trySelect("SELECT")) @@ -86,7 +84,7 @@ class SelectBuilderImplTest { resumed = true } } - val c = SelectBuilderImpl(delegate) + val c = SelectBuilderImpl(delegate) // still running builder check(!c.isSelected) check(c.trySelect("SELECT")) @@ -117,7 +115,7 @@ class SelectBuilderImplTest { resumed = true } } - val c = SelectBuilderImpl(delegate) + val c = SelectBuilderImpl(delegate) check(c.getResult() === COROUTINE_SUSPENDED) // suspend first check(!c.isSelected) check(c.trySelect("SELECT")) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt similarity index 88% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt index 3ab8955086..dcaa857916 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectDeferredTest.kt @@ -14,18 +14,19 @@ * limitations under the License. */ +@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 + package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class SelectDeferredTest : TestBase() { @Test - fun testSimpleReturnsImmediately() = runBlocking { + fun testSimpleReturnsImmediately() = runTest { expect(1) - val d1 = async(coroutineContext) { + val d1 = async(coroutineContext) { expect(3) 42 } @@ -43,9 +44,9 @@ class SelectDeferredTest : TestBase() { } @Test - fun testSimpleWithYield() = runBlocking { + fun testSimpleWithYield() = runTest { expect(1) - val d1 = async(coroutineContext) { + val d1 = async(coroutineContext) { expect(3) 42 } @@ -69,7 +70,7 @@ class SelectDeferredTest : TestBase() { } @Test - fun testSelectIncompleteLazy() = runBlocking { + fun testSelectIncompleteLazy() = runTest { expect(1) val d1 = async(coroutineContext, CoroutineStart.LAZY) { expect(5) @@ -97,9 +98,9 @@ class SelectDeferredTest : TestBase() { } @Test - fun testSelectTwo() = runBlocking { + fun testSelectTwo() = runTest { expect(1) - val d1 = async(coroutineContext) { + val d1 = async(coroutineContext) { expect(3) yield() // to the other deffered expect(5) @@ -107,7 +108,7 @@ class SelectDeferredTest : TestBase() { expect(7) "d1" } - val d2 = async(coroutineContext) { + val d2 = async(coroutineContext) { expect(4) "d2" // returns result } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt similarity index 97% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt index 16cffda545..3ca3e07f89 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectJobTest.kt @@ -17,9 +17,8 @@ package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class SelectJobTest : TestBase() { @Test diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt similarity index 68% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt index 96c94a4ac1..f2204dcb92 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexTest.kt @@ -18,9 +18,8 @@ package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.sync.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class SelectMutexTest : TestBase() { @Test @@ -48,7 +47,8 @@ class SelectMutexTest : TestBase() { expect(1) launch(coroutineContext) { expect(3) - val res = select { // will suspended + val res = select { + // will suspended mutex.onLock { assertTrue(mutex.isLocked) expect(6) @@ -66,27 +66,4 @@ class SelectMutexTest : TestBase() { yield() // to resumed select finish(8) } - - @Test - fun testSelectCancelledResourceRelease() = runTest { - val n = 1_000 * stressTestMultiplier - val mutex = Mutex(true) as MutexImpl // locked - expect(1) - repeat(n) { i -> - val job = launch(coroutineContext) { - expect(i + 2) - select { - mutex.onLock { - expectUnreached() // never able to lock - } - } - } - yield() // to the launched job, so that it suspends - job.cancel() // cancel the job and select - yield() // so it can cleanup after itself - } - assertTrue(mutex.isLocked) - assertTrue(mutex.isLockedEmptyQueueState) - finish(n + 2) - } } \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt similarity index 79% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt index b8842c19b4..26bb748b7b 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt @@ -16,15 +16,12 @@ package kotlinx.coroutines.experimental.selects -import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.core.IsEqual -import org.junit.Test +import kotlinx.coroutines.experimental.* +import kotlin.test.* class SelectTimeoutTest : TestBase() { @Test - fun testBasic() = runBlocking { + fun testBasic() = runTest { expect(1) val result = select { onTimeout(1000) { @@ -40,7 +37,7 @@ class SelectTimeoutTest : TestBase() { "FAIL" } } - assertThat(result, IsEqual("OK")) + assertEquals("OK", result) finish(3) } } \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt similarity index 79% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt index 1d3fd30cd6..050a103a86 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt @@ -17,14 +17,12 @@ package kotlinx.coroutines.experimental.sync import kotlinx.coroutines.experimental.* -import org.hamcrest.core.* -import org.junit.* -import org.junit.Assert.* +import kotlin.test.* import kotlin.coroutines.experimental.* class MutexTest : TestBase() { @Test - fun testSimple() = runBlocking { + fun testSimple() = runTest { val mutex = Mutex() expect(1) launch(coroutineContext) { @@ -64,7 +62,7 @@ class MutexTest : TestBase() { } @Test - fun withLockTest() = runBlocking { + fun withLockTest() = runTest { val mutex = Mutex() assertFalse(mutex.isLocked) mutex.withLock { @@ -73,26 +71,6 @@ class MutexTest : TestBase() { assertFalse(mutex.isLocked) } - @Test - fun testStress() = runBlocking { - val n = 1000 * stressTestMultiplier - val k = 100 - var shared = 0 - val mutex = Mutex() - val jobs = List(n) { - launch(CommonPool) { - repeat(k) { - mutex.lock() - shared++ - mutex.unlock() - } - } - } - jobs.forEach { it.join() } - println("Shared value = $shared") - assertEquals(n * k, shared) - } - @Test fun testUnconfinedStackOverflow() { val waiters = 10000 @@ -106,11 +84,11 @@ class MutexTest : TestBase() { } } mutex.unlock() // should not produce StackOverflowError - assertThat(done, IsEqual(waiters)) + assertEquals(waiters, done) } @Test - fun holdLock() = runBlocking { + fun holdLock() = runTest { val mutex = Mutex() val firstOwner = Any() val secondOwner = Any() @@ -121,7 +99,7 @@ class MutexTest : TestBase() { // owner firstOwner mutex.lock(firstOwner) - val secondLockJob = launch(CommonPool) { + val secondLockJob = launch { mutex.lock(secondOwner) } diff --git a/core/kotlinx-coroutines-core/README.md b/core/kotlinx-coroutines-core/README.md index ab1759e92c..1e1c7226a8 100644 --- a/core/kotlinx-coroutines-core/README.md +++ b/core/kotlinx-coroutines-core/README.md @@ -87,6 +87,10 @@ Select expression to perform multiple suspending operations simultaneously until Low-level primitives for finer-grained control of coroutines. +# Package kotlinx.coroutines.experimental.timeunit + +Optional time unit support for multiplatform projects. + [launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/launch.html diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.kt new file mode 100644 index 0000000000..21e4c78f46 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.kt @@ -0,0 +1,29 @@ +/* + * 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.internalAnnotations + +@Suppress("ACTUAL_WITHOUT_EXPECT") +internal actual typealias JvmName = kotlin.jvm.JvmName + +@Suppress("ACTUAL_WITHOUT_EXPECT") +internal actual typealias JvmMultifileClass = kotlin.jvm.JvmMultifileClass + +@Suppress("ACTUAL_WITHOUT_EXPECT") +internal actual typealias JvmField = kotlin.jvm.JvmField + +@Suppress("ACTUAL_WITHOUT_EXPECT") +internal actual typealias Volatile = kotlin.jvm.Volatile diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt index d54986c9c9..7d20709f4e 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt @@ -14,153 +14,13 @@ * limitations under the License. */ -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +@file:JvmMultifileClass +@file:JvmName("BuildersKt") package kotlinx.coroutines.experimental -import kotlinx.coroutines.experimental.intrinsics.* import java.util.concurrent.locks.* import kotlin.coroutines.experimental.* -import kotlin.coroutines.experimental.intrinsics.* - -// --------------- basic coroutine builders --------------- - -/** - * 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 can be explicitly specified. - * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`. - * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) - * of the parent coroutine may be used, - * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. - * The parent job may be also explicitly specified using [parent] parameter. - * - * 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. - * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case, - * the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function - * and will be started implicitly on the first invocation of [join][Job.join]. - * - * Uncaught exceptions in this coroutine cancel parent job in the context by default - * (unless [CoroutineExceptionHandler] is explicitly specified), which means that when `launch` is used with - * the context of another coroutine, then any uncaught exception leads to the cancellation of parent coroutine. - * - * 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 [DefaultDispatcher]. - * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. - * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). - * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). - * @param block the coroutine code. - */ -public actual fun launch( - context: CoroutineContext = DefaultDispatcher, - start: CoroutineStart = CoroutineStart.DEFAULT, - parent: Job? = null, - onCompletion: CompletionHandler? = null, - block: suspend CoroutineScope.() -> Unit -): Job { - val newContext = newCoroutineContext(context, parent) - val coroutine = if (start.isLazy) - LazyStandaloneCoroutine(newContext, block) else - StandaloneCoroutine(newContext, active = true) - if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) - coroutine.start(start, coroutine, block) - return coroutine -} - -/** @suppress **Deprecated**: Binary compatibility */ -@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) -public fun launch( - context: CoroutineContext = DefaultDispatcher, - start: CoroutineStart = CoroutineStart.DEFAULT, - parent: Job? = null, - block: suspend CoroutineScope.() -> Unit -): Job = launch(context, start, parent, block = block) - -/** @suppress **Deprecated**: Binary compatibility */ -@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) -public fun launch( - context: CoroutineContext = DefaultDispatcher, - start: CoroutineStart = CoroutineStart.DEFAULT, - block: suspend CoroutineScope.() -> Unit -): Job = - launch(context, start, block = block) - -/** - * @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter - */ -@Deprecated(message = "Use `start = CoroutineStart.XXX` parameter", - replaceWith = ReplaceWith("launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)")) -public fun launch(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> Unit): Job = - launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block = block) - -/** - * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns - * the result. - * - * This function immediately applies dispatcher from the new context, shifting execution of the block into the - * different thread inside the block, and back when it completes. - * The specified [context] is added onto the current coroutine context for the execution of the block. - * - * An optional `start` parameter is used only if the specified `context` uses a different [CoroutineDispatcher] than - * 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 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]. - */ -public actual suspend fun withContext( - context: CoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, - block: suspend () -> T -): T = suspendCoroutineOrReturn sc@ { cont -> - val oldContext = cont.context - // fast path #1 if there is no change in the actual context: - if (context === oldContext || context is CoroutineContext.Element && oldContext[context.key] === context) - return@sc block.startCoroutineUninterceptedOrReturn(cont) - // compute new context - val newContext = oldContext + context - // fast path #2 if the result is actually the same - if (newContext === oldContext) - return@sc block.startCoroutineUninterceptedOrReturn(cont) - // fast path #3 if the new dispatcher is the same as the old one. - // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher) - if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) { - val newContinuation = RunContinuationDirect(newContext, cont) - return@sc block.startCoroutineUninterceptedOrReturn(newContinuation) - } - // slowest path otherwise -- use new interceptor, sync to its result via a full-blown instance of RunCompletion - require(!start.isLazy) { "$start start is not supported" } - val completion = RunCompletion( - context = newContext, - delegate = cont, - resumeMode = if (start == CoroutineStart.ATOMIC) MODE_ATOMIC_DEFAULT else MODE_CANCELLABLE) - completion.initParentJobInternal(newContext[Job]) // attach to job - @Suppress("DEPRECATION") - start(block, completion) - completion.getResult() -} - -/** @suppress **Deprecated**: Renamed to [withContext]. */ -@Deprecated(message = "Renamed to `withContext`", level=DeprecationLevel.WARNING, - replaceWith = ReplaceWith("withContext(context, start, block)")) -public suspend fun run( - context: CoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, - block: suspend () -> T -): T = - withContext(context, start, block) - -/** @suppress **Deprecated** */ -@Deprecated(message = "It is here for binary compatibility only", level=DeprecationLevel.HIDDEN) -public suspend fun run(context: CoroutineContext, block: suspend () -> T): T = - withContext(context, start = CoroutineStart.ATOMIC, block = block) /** * Runs new coroutine and **blocks** current thread _interruptibly_ until its completion. @@ -198,41 +58,6 @@ public fun runBlocking(context: CoroutineContext = EmptyCoroutineContext, bl return coroutine.joinBlocking() } -// --------------- implementation --------------- - -private open class StandaloneCoroutine( - private val parentContext: CoroutineContext, - active: Boolean -) : AbstractCoroutine(parentContext, active) { - override fun hasOnFinishingHandler(update: Any?) = update is CompletedExceptionally - override fun onFinishingInternal(update: Any?) { - // note the use of the parent's job context below! - if (update is CompletedExceptionally) handleCoroutineException(parentContext, update.exception) - } -} - -private class LazyStandaloneCoroutine( - parentContext: CoroutineContext, - private val block: suspend CoroutineScope.() -> Unit -) : StandaloneCoroutine(parentContext, active = false) { - override fun onStart() { - block.startCoroutineCancellable(this, this) - } -} - -private class RunContinuationDirect( - override val context: CoroutineContext, - continuation: Continuation -) : Continuation by continuation - - -@Suppress("UNCHECKED_CAST") -private class RunCompletion( - override val context: CoroutineContext, - delegate: Continuation, - resumeMode: Int -) : AbstractContinuation(delegate, resumeMode) - private class BlockingCoroutine( parentContext: CoroutineContext, private val blockedThread: Thread, diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt index a73ad003aa..10081ef209 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt @@ -16,6 +16,7 @@ package kotlinx.coroutines.experimental +import kotlinx.coroutines.experimental.timeunit.TimeUnit import java.util.concurrent.* import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.experimental.CoroutineContext diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt new file mode 100644 index 0000000000..0f5cd77fc8 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt @@ -0,0 +1,27 @@ +/* + * 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.internal.* + +internal actual abstract class CompletionHandlerNode actual constructor() : LockFreeLinkedListNode(), CompletionHandler { + actual inline val asHandler: CompletionHandler get() = this + actual abstract override fun invoke(cause: Throwable?) +} + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun CompletionHandler.invokeIt(cause: Throwable?) = invoke(cause) \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt index 832e1f0307..774ebe78eb 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt @@ -41,30 +41,6 @@ internal fun resetCoroutineId() { COROUTINE_ID.set(0) } -/** - * A coroutine dispatcher that is not confined to any specific thread. - * It executes initial continuation of the coroutine _right here_ in the current call-frame - * and let the coroutine resume in whatever thread that is used by the corresponding suspending function, without - * mandating any specific threading policy. - * - * Note, that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption, - * but still want to execute it in the current call-frame until its first suspension, then you can use - * an optional [CoroutineStart] parameter in coroutine builders like [launch] and [async] setting it to the - * the value of [CoroutineStart.UNDISPATCHED]. - */ -public actual object Unconfined : CoroutineDispatcher() { - actual override fun isDispatchNeeded(context: CoroutineContext): Boolean = false - actual override fun dispatch(context: CoroutineContext, block: Runnable) { throw UnsupportedOperationException() } - override fun toString(): String = "Unconfined" -} - -/** - * @suppress **Deprecated**: `Here` was renamed to `Unconfined`. - */ -@Deprecated(message = "`Here` was renamed to `Unconfined`", - replaceWith = ReplaceWith(expression = "Unconfined")) -public typealias Here = Unconfined - /** * 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. @@ -94,7 +70,7 @@ public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool * The string "coroutine" is used as a default name. */ @JvmOverloads // for binary compatibility with newCoroutineContext(context: CoroutineContext) version -public fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext { +public actual fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext { val debug = if (DEBUG) context + CoroutineId(COROUTINE_ID.incrementAndGet()) else context val wp = if (parent == null) debug else debug + parent return if (context !== DefaultDispatcher && context[ContinuationInterceptor] == null) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandler.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerImpl.kt similarity index 56% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandler.kt rename to core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerImpl.kt index 2141027c42..04246d3cec 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandler.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerImpl.kt @@ -16,14 +16,16 @@ package kotlinx.coroutines.experimental +import java.util.* +import kotlin.coroutines.experimental.AbstractCoroutineContextElement import kotlin.coroutines.experimental.CoroutineContext -public expect fun handleCoroutineException(context: CoroutineContext, exception: Throwable) - -public expect interface CoroutineExceptionHandler : CoroutineContext.Element { - public companion object Key : CoroutineContext.Key - public fun handleException(context: CoroutineContext, exception: Throwable) +internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { + // use additional extension handlers + ServiceLoader.load(CoroutineExceptionHandler::class.java).forEach { handler -> + handler.handleException(context, exception) + } + // use thread's handler + val currentThread = Thread.currentThread() + currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) } - -@Suppress("FunctionName") -public expect fun CoroutineExceptionHandler(handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Debug.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Debug.kt index 6930f336da..a86d98f4de 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Debug.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Debug.kt @@ -20,7 +20,7 @@ import kotlin.coroutines.experimental.Continuation // internal debugging tools -internal val Any.hexAddress: String +internal actual val Any.hexAddress: String get() = Integer.toHexString(System.identityHashCode(this)) internal fun Any?.toSafeString(): String = diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/DefaultExecutor.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/DefaultExecutor.kt index d2aab07ac9..7d7d67dbc3 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/DefaultExecutor.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/DefaultExecutor.kt @@ -16,7 +16,9 @@ package kotlinx.coroutines.experimental -import java.util.concurrent.* +import kotlinx.coroutines.experimental.timeunit.* + +internal actual val DefaultDelay: Delay = DefaultExecutor @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") internal object DefaultExecutor : EventLoopBase(), Runnable { diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt index e752994a5a..6d6685beff 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt @@ -18,7 +18,7 @@ package kotlinx.coroutines.experimental import kotlinx.atomicfu.* import kotlinx.coroutines.experimental.internal.* -import java.util.concurrent.* +import kotlinx.coroutines.experimental.timeunit.TimeUnit import java.util.concurrent.locks.* import kotlin.coroutines.experimental.* diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt index ed43104e5c..7d7c532af1 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt @@ -26,9 +26,12 @@ public actual class CompletionHandlerException actual constructor( cause: Throwable ) : RuntimeException(message, cause) - /** - * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending. - */ +/** + * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending. + * It indicates _normal_ cancellation of a coroutine. + * **It is not printed to console/log by default uncaught exception handler**. + * (see [handleCoroutineException]). +*/ public actual typealias CancellationException = java.util.concurrent.CancellationException /** @@ -53,31 +56,8 @@ public actual class JobCancellationException public actual constructor( (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0) } -/** - * This exception is thrown by [withTimeout] to indicate timeout. - */ -@Suppress("DEPRECATION") -public actual class TimeoutCancellationException internal constructor( - message: String, - @JvmField internal val coroutine: Job? -) : TimeoutException(message) { - /** - * Creates timeout exception with a given message. - */ - public actual constructor(message: String) : this(message, null) -} - -@Suppress("FunctionName") -internal fun TimeoutCancellationException( - time: Long, - unit: TimeUnit, - coroutine: Job -) : TimeoutCancellationException = TimeoutCancellationException("Timed out waiting for $time $unit", coroutine) - -/** - * @suppress **Deprecated**: Renamed to TimeoutCancellationException - */ -@Deprecated("Renamed to TimeoutCancellationException", replaceWith = ReplaceWith("TimeoutCancellationException")) -public open class TimeoutException(message: String) : CancellationException(message) - internal actual class DispatchException actual constructor(message: String, cause: Throwable) : RuntimeException(message, cause) + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) = + addSuppressed(other) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt index 545300fabe..f20753a7bc 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt @@ -16,13 +16,10 @@ package kotlinx.coroutines.experimental -import java.io.Closeable -import java.util.concurrent.Executor -import java.util.concurrent.ExecutorService -import java.util.concurrent.RejectedExecutionException -import java.util.concurrent.ScheduledExecutorService -import java.util.concurrent.TimeUnit -import kotlin.coroutines.experimental.CoroutineContext +import kotlinx.coroutines.experimental.timeunit.TimeUnit +import java.io.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* /** * [CoroutineDispatcher] that implements [Closeable] @@ -108,3 +105,14 @@ private class ResumeUndispatchedRunnable( with(continuation) { dispatcher.resumeUndispatched(Unit) } } } + +/** + * An implementation of [DisposableHandle] that cancels the specified future on dispose. + * @suppress **This is unstable API and it is subject to change.** + */ +public class DisposableFutureHandle(private val future: Future<*>) : DisposableHandle { + override fun dispose() { + future.cancel(false) + } + override fun toString(): String = "DisposableFutureHandle[$future]" +} diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt new file mode 100644 index 0000000000..b10f0cdcf7 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.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. + */ + +@file:JvmMultifileClass +@file:JvmName("JobKt") + +package kotlinx.coroutines.experimental + +import java.util.concurrent.* + +/** + * 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)) + +private class CancelFutureOnCompletion( + job: Job, + private val future: Future<*> +) : JobNode(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]" +} + diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.kt new file mode 100644 index 0000000000..83e239d87b --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.kt @@ -0,0 +1,29 @@ +/* + * 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 + +/** + * A runnable task for [CoroutineDispatcher.dispatch]. + */ +public actual typealias Runnable = java.lang.Runnable + +/** + * Creates [Runnable] task instance. + */ +@Suppress("FunctionName") +public actual inline fun Runnable(crossinline block: () -> Unit): Runnable = + java.lang.Runnable { block() } diff --git a/core/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 index a42517a0d7..4ce28dad7b 100644 --- a/core/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 @@ -138,7 +138,7 @@ public abstract class AbstractSendChannel : SendChannel { */ protected fun describeSendBuffered(element: E): AddLastDesc<*> = SendBufferedDesc(queue, element) - private open class SendBufferedDesc( + private open class SendBufferedDesc( queue: LockFreeLinkedListHead, element: E ) : AddLastDesc>(queue, SendBuffered(element)) { @@ -153,7 +153,7 @@ public abstract class AbstractSendChannel : SendChannel { */ protected fun describeSendConflated(element: E): AddLastDesc<*> = SendConflatedDesc(queue, element) - private class SendConflatedDesc( + private class SendConflatedDesc( queue: LockFreeLinkedListHead, element: E ) : SendBufferedDesc(queue, element) { @@ -647,7 +647,7 @@ public abstract class AbstractChannel : AbstractSendChannel(), Channel } } - private inner class TryEnqueueReceiveDesc( + private inner class TryEnqueueReceiveDesc( select: SelectInstance, block: suspend (E?) -> R, nullOnClose: Boolean diff --git a/core/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 index d10bcf618e..54adc81a9f 100644 --- a/core/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 @@ -172,7 +172,8 @@ private class LazyActorCoroutine( parentContext: CoroutineContext, channel: Channel, private val block: suspend ActorScope.() -> Unit -) : ActorCoroutine(parentContext, channel, active = false), SelectClause2> { +) : ActorCoroutine(parentContext, channel, active = false), + SelectClause2> { override fun onStart() { block.startCoroutineCancellable(this, this) } diff --git a/core/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 index 06e93847c7..17a0ebbc7c 100644 --- a/core/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 @@ -41,15 +41,14 @@ internal val LIST_EMPTY: Any = Symbol("LIST_EMPTY") private val REMOVE_PREPARED: Any = Symbol("REMOVE_PREPARED") -/** - * @suppress **This is unstable API and it is subject to change.** - */ +/** @suppress **This is unstable API and it is subject to change.** */ public typealias RemoveFirstDesc = LockFreeLinkedListNode.RemoveFirstDesc -/** - * @suppress **This is unstable API and it is subject to change.** - */ -public typealias AddLastDesc = LockFreeLinkedListNode.AddLastDesc +/** @suppress **This is unstable API and it is subject to change.** */ +public actual typealias AddLastDesc = LockFreeLinkedListNode.AddLastDesc + +/** @suppress **This is unstable API and it is subject to change.** */ +public actual typealias AbstractAtomicDesc = LockFreeLinkedListNode.AbstractAtomicDesc /** * Doubly-linked concurrent list node with remove support. @@ -67,7 +66,7 @@ public typealias AddLastDesc = LockFreeLinkedListNode.AddLastDesc * @suppress **This is unstable API and it is subject to change.** */ @Suppress("LeakingThis") -public open class LockFreeLinkedListNode { +public actual open class LockFreeLinkedListNode { private val _next = atomic(this) // Node | Removed | OpDescriptor private val _prev = atomic(this) // Node | Removed private val _removedRef = atomic(null) // lazily cached removed ref to this @@ -99,7 +98,7 @@ public open class LockFreeLinkedListNode { public val isFresh: Boolean get() = _next.value === this - public val isRemoved: Boolean get() = next is Removed + public actual val isRemoved: Boolean get() = next is Removed // LINEARIZABLE. Returns Node | Removed public val next: Any get() { @@ -109,6 +108,8 @@ public open class LockFreeLinkedListNode { } } + public actual val nextNode: Node get() = next.unwrap() + // LINEARIZABLE. Returns Node | Removed public val prev: Any get() { _prev.loop { prev -> @@ -119,9 +120,11 @@ public open class LockFreeLinkedListNode { } } + public actual val prevNode: Node get() = prev.unwrap() + // ------ addOneIfEmpty ------ - public fun addOneIfEmpty(node: Node): Boolean { + public actual fun addOneIfEmpty(node: Node): Boolean { node._prev.lazySet(this) node._next.lazySet(this) while (true) { @@ -140,7 +143,7 @@ public open class LockFreeLinkedListNode { /** * Adds last item to this list. */ - public fun addLast(node: Node) { + public actual fun addLast(node: Node) { while (true) { // lock-free loop on prev.next val prev = prev as Node // sentinel node is never removed, so prev is always defined if (prev.addNext(node, this)) return @@ -152,7 +155,7 @@ public open class LockFreeLinkedListNode { /** * Adds last item to this list atomically if the [condition] is true. */ - public inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean { + public actual inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean { val condAdd = makeCondAddOp(node, condition) while (true) { // lock-free loop on prev.next val prev = prev as Node // sentinel node is never removed, so prev is always defined @@ -163,7 +166,7 @@ public open class LockFreeLinkedListNode { } } - public inline fun addLastIfPrev(node: Node, predicate: (Node) -> Boolean): Boolean { + public actual inline fun addLastIfPrev(node: Node, predicate: (Node) -> Boolean): Boolean { while (true) { // lock-free loop on prev.next val prev = prev as Node // sentinel node is never removed, so prev is always defined if (!predicate(prev)) return false @@ -171,7 +174,7 @@ public open class LockFreeLinkedListNode { } } - public inline fun addLastIfPrevAndIf( + public actual inline fun addLastIfPrevAndIf( node: Node, predicate: (Node) -> Boolean, // prev node predicate crossinline condition: () -> Boolean // atomically checked condition @@ -239,7 +242,7 @@ public open class LockFreeLinkedListNode { * Removes this node from the list. Returns `true` when removed successfully, or `false` if the node was already * removed or if it was not added to any list in the first place. */ - public open fun remove(): Boolean { + public actual open fun remove(): Boolean { while (true) { // lock-free loop on next val next = this.next if (next is Removed) return false // was already removed -- don't try to help (original thread will take care) @@ -273,7 +276,7 @@ public open class LockFreeLinkedListNode { } } - public fun removeFirstOrNull(): Node? { + public actual fun removeFirstOrNull(): Node? { while (true) { // try to linearize val first = next as Node if (first === this) return null @@ -295,7 +298,7 @@ public open class LockFreeLinkedListNode { } // just peek at item when predicate is true - public inline fun removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? { + public actual inline fun removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? { while (true) { // try to linearize val first = next as Node if (first === this) return null @@ -308,7 +311,7 @@ public open class LockFreeLinkedListNode { // ------ multi-word atomic operations helpers ------ - public open class AddLastDesc( + public open class AddLastDesc actual constructor( @JvmField val queue: Node, @JvmField val node: T ) : AbstractAtomicDesc() { @@ -340,7 +343,7 @@ public open class LockFreeLinkedListNode { override fun retry(affected: Node, next: Any): Boolean = next !== queue - override fun onPrepare(affected: Node, next: Node): Any? { + protected override fun onPrepare(affected: Node, next: Node): Any? { // Note: onPrepare must use CAS to make sure the stale invocation is not // going to overwrite the previous decision on successful preparation. // Result of CAS is irrelevant, but we must ensure that it is set when invoker completes @@ -659,20 +662,20 @@ private class Removed(@JvmField val ref: Node) { } @PublishedApi -internal fun Any.unwrap(): Node = if (this is Removed) ref else this as Node +internal fun Any.unwrap(): Node = (this as? Removed)?.ref ?: this as Node /** * Head (sentinel) item of the linked list that is never removed. * * @suppress **This is unstable API and it is subject to change.** */ -public open class LockFreeLinkedListHead : LockFreeLinkedListNode() { - public val isEmpty: Boolean get() = next === this +public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { + public actual val isEmpty: Boolean get() = next === this /** * Iterates over all elements in this list of a specified type. */ - public inline fun forEach(block: (T) -> Unit) { + public actual inline fun forEach(block: (T) -> Unit) { var cur: Node = next as Node while (cur != this) { if (cur is T) block(cur) @@ -681,8 +684,9 @@ public open class LockFreeLinkedListHead : LockFreeLinkedListNode() { } // just a defensive programming -- makes sure that list head sentinel is never removed - public final override fun remove() = throw UnsupportedOperationException() - public final override fun describeRemove(): AtomicDesc? = throw UnsupportedOperationException() + public actual final override fun remove(): Nothing = throw UnsupportedOperationException() + + public final override fun describeRemove(): Nothing = throw UnsupportedOperationException() internal fun validate() { var prev: Node = this diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.kt new file mode 100644 index 0000000000..8c0a66c55f --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.kt @@ -0,0 +1,23 @@ +/* + * 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.timeunit + +/** + * Time unit type alias for writing multiplatform code. + */ +@Suppress("ACTUAL_WITHOUT_EXPECT") +public actual typealias TimeUnit = java.util.concurrent.TimeUnit \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncJvmTest.kt similarity index 98% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncJvmTest.kt index 7b93917505..a2f4d5cbcd 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncJvmTest.kt @@ -19,7 +19,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class AsyncTest : TestBase() { +class AsyncJvmTest : TestBase() { // This must be a common test but it fails on JS because of KT-21961 @Test fun testAsyncWithFinally() = runTest { diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt index 9d7a20a839..1ea089a53e 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AtomicCancellationTest.kt @@ -23,44 +23,6 @@ import kotlin.coroutines.experimental.* import kotlin.test.* class AtomicCancellationTest : TestBase() { - @Test - fun testLockAtomicCancel() = runBlocking { - expect(1) - val mutex = Mutex(true) // locked mutex - val job = launch(coroutineContext, start = CoroutineStart.UNDISPATCHED) { - expect(2) - mutex.lock() // suspends - expect(4) // should execute despite cancellation - } - expect(3) - mutex.unlock() // unlock mutex first - job.cancel() // cancel the job next - yield() // now yield - finish(5) - } - - @Test - fun testSelectLockAtomicCancel() = runBlocking { - expect(1) - val mutex = Mutex(true) // locked mutex - val job = launch(coroutineContext, start = CoroutineStart.UNDISPATCHED) { - expect(2) - val result = select { // suspends - mutex.onLock { - expect(4) - "OK" - } - } - assertEquals("OK", result) - expect(5) // should execute despite cancellation - } - expect(3) - mutex.unlock() // unlock mutex first - job.cancel() // cancel the job next - yield() // now yield - finish(6) - } - @Test fun testSendAtomicCancel() = runBlocking { expect(1) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesJvmTest.kt similarity index 97% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesJvmTest.kt index 58e7bfeaa2..b200015fbf 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesJvmTest.kt @@ -19,7 +19,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* import kotlin.test.* -class CoroutinesTest : TestBase() { +class CoroutinesJvmTest : TestBase() { @Test fun testNotCancellableCodeWithExceptionCancelled() = runTest { expect(1) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobStressTest.kt similarity index 96% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobStressTest.kt index 06f7c68707..50ef98ed18 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JobStressTest.kt @@ -18,7 +18,7 @@ package kotlinx.coroutines.experimental import kotlin.test.* -class JobTest : TestBase() { +class JobStressTest : TestBase() { @Test fun testMemoryRelease() { val job = Job() diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt index 4edc6e9662..f6f087b38a 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt @@ -14,9 +14,6 @@ * limitations under the License. */ -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - package kotlinx.coroutines.experimental import org.junit.After diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCommonPoolTest.kt similarity index 96% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCommonPoolTest.kt index ee26c07b04..993aeefb87 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCommonPoolTest.kt @@ -18,7 +18,7 @@ package kotlinx.coroutines.experimental import kotlin.test.* -class WithContextTest : TestBase() { +class WithContextCommonPoolTest : TestBase() { @Test fun testCommonPoolNoSuspend() = runTest { expect(1) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullJvmTest.kt similarity index 51% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullJvmTest.kt index 6c43f764b0..560d087309 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullJvmTest.kt @@ -18,55 +18,7 @@ package kotlinx.coroutines.experimental import kotlin.test.* -class WithTimeoutOrNullTest : TestBase() { - @Test - fun testNullOnTimeout() = runTest { - expect(1) - val result = withTimeoutOrNull(100) { - expect(2) - delay(1000) - expectUnreached() - "OK" - } - assertEquals(null, result) - finish(3) - } - - @Test - fun testSuppressExceptionWithResult() = runTest { - expect(1) - val result = withTimeoutOrNull(100) { - expect(2) - try { - delay(1000) - } catch (e: CancellationException) { - expect(3) - } - "OK" - } - assertEquals(null, result) - finish(4) - } - - @Test - fun testSuppressExceptionWithAnotherException() = runTest( - expected = { it is TestException } - ) { - expect(1) - val result = withTimeoutOrNull(100) { - expect(2) - try { - delay(1000) - } catch (e: CancellationException) { - finish(3) - throw TestException() - } - expectUnreached() - "OK" - } - expectUnreached() - } - +class WithTimeoutOrNullJvmTest : TestBase() { @Test fun testOuterTimeoutFiredBeforeInner() = runTest { val result = withTimeoutOrNull(100) { @@ -81,6 +33,4 @@ class WithTimeoutOrNullTest : TestBase() { // outer timeout results in null assertEquals(null, result) } - - private class TestException : Exception() } \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullThreadDispatchTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullThreadDispatchTest.kt index df1387ce89..f5b8562136 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullThreadDispatchTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullThreadDispatchTest.kt @@ -47,7 +47,6 @@ class WithTimeoutOrNullThreadDispatchTest : TestBase() { } } - @Test fun testCancellationDispatchCustomNoDelay() { // it also checks that there is at most once scheduled request in flight (no spurious concurrency) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt deleted file mode 100644 index a793a1e5c7..0000000000 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt +++ /dev/null @@ -1,76 +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. - */ - -@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 - -package kotlinx.coroutines.experimental - -import kotlin.test.* -import java.io.IOException - -class WithTimeoutTest : TestBase() { - @Test - fun testExceptionOnTimeout() = runTest { - expect(1) - try { - withTimeout(100) { - expect(2) - delay(1000) - expectUnreached() - "OK" - } - } catch (e: CancellationException) { - assertEquals("Timed out waiting for 100 MILLISECONDS", e.message) - finish(3) - } - } - - @Test - fun testSuppressExceptionWithResult() = runTest( - expected = { it is CancellationException } - ) { - expect(1) - val result = withTimeout(100) { - expect(2) - try { - delay(1000) - } catch (e: CancellationException) { - finish(3) - } - "OK" - } - expectUnreached() - } - - @Test - fun testSuppressExceptionWithAnotherException() = runTest( - expected = { it is IOException } - ) { - expect(1) - withTimeout(100) { - expect(2) - try { - delay(1000) - } catch (e: CancellationException) { - finish(3) - throw IOException(e) - } - expectUnreached() - "OK" - } - expectUnreached() - } -} \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt index 7f341c1655..60bdeb1a5f 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt @@ -18,10 +18,10 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.selects.* +import kotlinx.coroutines.experimental.timeunit.* import org.junit.* import org.junit.runner.* import org.junit.runners.* -import java.util.concurrent.* import java.util.concurrent.atomic.* import kotlin.coroutines.experimental.* diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelSubStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelSubStressTest.kt index 21b238cb9d..cfef04022d 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelSubStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelSubStressTest.kt @@ -17,10 +17,10 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.timeunit.* import org.junit.* import org.junit.runner.* import org.junit.runners.* -import java.util.concurrent.* import java.util.concurrent.atomic.* import kotlin.coroutines.experimental.* diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexStressTest.kt new file mode 100644 index 0000000000..d7be9ee0f4 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectMutexStressTest.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.selects + +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.sync.* +import kotlin.test.* + +class SelectMutexStressTest : TestBase() { + @Test + fun testSelectCancelledResourceRelease() = runTest { + val n = 1_000 * stressTestMultiplier + val mutex = Mutex(true) as MutexImpl // locked + expect(1) + repeat(n) { i -> + val job = launch(kotlin.coroutines.experimental.coroutineContext) { + expect(i + 2) + select { + mutex.onLock { + expectUnreached() // never able to lock + } + } + } + yield() // to the launched job, so that it suspends + job.cancel() // cancel the job and select + yield() // so it can cleanup after itself + } + assertTrue(mutex.isLocked) + assertTrue(mutex.isLockedEmptyQueueState) + finish(n + 2) + } +} \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexStressTest.kt new file mode 100644 index 0000000000..720bd3b850 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexStressTest.kt @@ -0,0 +1,42 @@ +/* + * 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.sync + +import kotlinx.coroutines.experimental.* +import kotlin.test.* + +class MutexStressTest : TestBase() { + @Test + fun testStress() = runTest { + val n = 1000 * stressTestMultiplier + val k = 100 + var shared = 0 + val mutex = Mutex() + val jobs = List(n) { + launch(CommonPool) { + repeat(k) { + mutex.lock() + shared++ + mutex.unlock() + } + } + } + jobs.forEach { it.join() } + println("Shared value = $shared") + assertEquals(n * k, shared) + } +} \ No newline at end of file diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt deleted file mode 100644 index 6b31a911be..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt +++ /dev/null @@ -1,102 +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.intrinsics.COROUTINE_SUSPENDED - -private const val UNDECIDED = 0 -private const val SUSPENDED = 1 -private const val RESUMED = 2 - -/** - * @suppress **This is unstable API and it is subject to change.** - */ -internal abstract class AbstractContinuation( - public final override val delegate: Continuation, - public final override val resumeMode: Int -) : JobSupport(true), Continuation, DispatchedTask { - private var decision = UNDECIDED - - /* decision state machine - - +-----------+ trySuspend +-----------+ - | UNDECIDED | -------------> | SUSPENDED | - +-----------+ +-----------+ - | - | tryResume - V - +-----------+ - | RESUMED | - +-----------+ - - Note: both tryResume and trySuspend can be invoked at most once, first invocation wins - */ - - override fun takeState(): Any? = state - - private fun trySuspend(): Boolean = when (decision) { - UNDECIDED -> { decision = SUSPENDED; true } - RESUMED -> false - else -> error("Already suspended") - } - - private fun tryResume(): Boolean = when (decision) { - UNDECIDED -> { decision = RESUMED; true } - SUSPENDED -> false - else -> error("Already resumed") - } - - @PublishedApi - internal fun getResult(): Any? { - if (trySuspend()) return COROUTINE_SUSPENDED - // otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state - val state = this.state - if (state is CompletedExceptionally) throw state.exception - return getSuccessfulResult(state) - } - - internal final override fun onCompletionInternal(state: Any?, mode: Int) { - if (tryResume()) return // completed before getResult invocation -- bail out - // otherwise, getResult has already commenced, i.e. completed later or in other thread - dispatch(mode) - } - - override fun resume(value: T) = - resumeImpl(value, resumeMode) - - override fun resumeWithException(exception: Throwable) = - resumeImpl(CompletedExceptionally(exception), resumeMode) - - protected fun resumeImpl(proposedUpdate: Any?, resumeMode: Int) { - val state = this.state - return when (state) { - is Incomplete -> updateState(proposedUpdate, resumeMode) - is Cancelled -> { - // Ignore resumes in cancelled coroutines, but handle exception if a different one here - if (proposedUpdate is CompletedExceptionally && proposedUpdate.exception != state.exception) - handleException(proposedUpdate.exception) - return - } - else -> error("Already resumed, but got $proposedUpdate") - } - } - - override fun handleException(exception: Throwable) { - handleCoroutineException(context, exception) - } -} \ No newline at end of file diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.kt new file mode 100644 index 0000000000..26c19ae86e --- /dev/null +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Annotations.kt @@ -0,0 +1,27 @@ +/* + * 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.internalAnnotations + +@Target(AnnotationTarget.FILE) +internal actual annotation class JvmName(actual val name: String) + +@Target(AnnotationTarget.FILE) +internal actual annotation class JvmMultifileClass + +internal actual annotation class JvmField + +internal actual annotation class Volatile diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt deleted file mode 100644 index 8a36f27c9e..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt +++ /dev/null @@ -1,280 +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. - */ - -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - -package kotlinx.coroutines.experimental - -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn -import kotlin.coroutines.experimental.suspendCoroutine - -// --------------- cancellable continuations --------------- - -/** - * Cancellable continuation. Its job is _completed_ when it is resumed or cancelled. - * When [cancel] function is explicitly invoked, this continuation immediately resumes with [CancellationException] or - * with the specified cancel cause. - * - * Cancellable continuation has three states (as subset of [Job] states): - * - * | **State** | [isActive] | [isCompleted] | [isCancelled] | - * | ----------------------------------- | ---------- | ------------- | ------------- | - * | _Active_ (initial state) | `true` | `false` | `false` | - * | _Resumed_ (final _completed_ state) | `false` | `true` | `false` | - * | _Canceled_ (final _completed_ state)| `false` | `true` | `true` | - * - * Invocation of [cancel] transitions this continuation from _active_ to _cancelled_ state, while - * invocation of [resume] or [resumeWithException] transitions it from _active_ to _resumed_ state. - * - * A [cancelled][isCancelled] continuation implies that it is [completed][isCompleted]. - * - * Invocation of [resume] or [resumeWithException] in _resumed_ state produces [IllegalStateException] - * but is ignored in _cancelled_ state. - * - * ``` - * +-----------+ resume +---------+ - * | Active | ----------> | Resumed | - * +-----------+ +---------+ - * | - * | cancel - * V - * +-----------+ - * | Cancelled | - * +-----------+ - * - * ``` - */ -public actual interface CancellableContinuation : Continuation { - /** - * Returns `true` when this continuation is active -- it has not completed or cancelled yet. - */ - public actual val isActive: Boolean - - /** - * Returns `true` when this continuation has completed for any reason. A continuation - * that was cancelled is also considered complete. - */ - public actual val isCompleted: Boolean - - /** - * Returns `true` if this continuation was [cancelled][cancel]. - * - * It implies that [isActive] is `false` and [isCompleted] is `true`. - */ - public actual val isCancelled: Boolean - - /** - * Tries to resume this continuation with a given value and returns non-null object token if it was successful, - * or `null` otherwise (it was already resumed or cancelled). When non-null object was returned, - * [completeResume] must be invoked with it. - * - * When [idempotent] is not `null`, this function performs _idempotent_ operation, so that - * further invocations with the same non-null reference produce the same result. - * - * @suppress **This is unstable API and it is subject to change.** - */ - public actual fun tryResume(value: T, idempotent: Any? = null): Any? - - /** - * Tries to resume this continuation with a given exception and returns non-null object token if it was successful, - * or `null` otherwise (it was already resumed or cancelled). When non-null object was returned, - * [completeResume] must be invoked with it. - * - * @suppress **This is unstable API and it is subject to change.** - */ - public fun tryResumeWithException(exception: Throwable): Any? - - /** - * Completes the execution of [tryResume] or [tryResumeWithException] on its non-null result. - * - * @suppress **This is unstable API and it is subject to change.** - */ - public actual fun completeResume(token: Any) - - /** - * Makes this continuation cancellable. Use it with `holdCancellability` optional parameter to - * [suspendCancellableCoroutine] function. It throws [IllegalStateException] if invoked more than once. - */ - public actual fun initCancellability() - - /** - * Cancels this continuation with an optional cancellation [cause]. The result is `true` if this continuation was - * cancelled as a result of this invocation and `false` otherwise. - */ - public actual fun cancel(cause: Throwable? = null): Boolean - - /** - * Registers handler that is **synchronously** invoked once on completion of this continuation. - * When continuation is already complete, then the handler is immediately invoked - * with continuation's exception or `null`. Otherwise, handler will be invoked once when this - * continuation 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 continuation. The references to - * all the handlers are released when this continuation 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. - */ - public actual fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle - - /** - * Resumes this continuation with a given [value] in the invoker thread without going though - * [dispatch][CoroutineDispatcher.dispatch] function of the [CoroutineDispatcher] in the [context]. - * This function is designed to be used only by the [CoroutineDispatcher] implementations themselves. - * **It should not be used in general code**. - */ - public actual fun CoroutineDispatcher.resumeUndispatched(value: T) - - /** - * Resumes this continuation with a given [exception] in the invoker thread without going though - * [dispatch][CoroutineDispatcher.dispatch] function of the [CoroutineDispatcher] in the [context]. - * This function is designed to be used only by the [CoroutineDispatcher] implementations themselves. - * **It should not be used in general code**. - */ - public actual fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable) -} - -/** - * Suspends coroutine similar to [suspendCoroutine], but provide an implementation of [CancellableContinuation] to - * the [block]. This function throws [CancellationException] if the coroutine is cancelled or completed while suspended. - * - * If [holdCancellability] optional parameter is `true`, then the coroutine is suspended, but it is not - * cancellable until [CancellableContinuation.initCancellability] is invoked. - * - * See [suspendAtomicCancellableCoroutine] for suspending functions that need *atomic cancellation*. - */ -public actual inline suspend fun suspendCancellableCoroutine( - holdCancellability: Boolean = false, - crossinline block: (CancellableContinuation) -> Unit -): T = - suspendCoroutineOrReturn { cont -> - val cancellable = CancellableContinuationImpl(cont, resumeMode = MODE_CANCELLABLE) - if (!holdCancellability) cancellable.initCancellability() - block(cancellable) - cancellable.getResult() -} - -/** - * Suspends coroutine similar to [suspendCancellableCoroutine], but with *atomic cancellation*. - * - * When suspended function throws [CancellationException] it means that the continuation was not resumed. - * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may - * continue to execute even after it was cancelled from the same thread in the case when the continuation - * was already resumed and was posted for execution to the thread's queue. - */ -public actual inline suspend fun suspendAtomicCancellableCoroutine( - holdCancellability: Boolean = false, - crossinline block: (CancellableContinuation) -> Unit -): T = - suspendCoroutineOrReturn { cont -> - val cancellable = CancellableContinuationImpl(cont, resumeMode = MODE_ATOMIC_DEFAULT) - if (!holdCancellability) cancellable.initCancellability() - block(cancellable) - cancellable.getResult() - } - -// --------------- implementation details --------------- - -@PublishedApi -internal class CancellableContinuationImpl( - delegate: Continuation, - resumeMode: Int -) : AbstractContinuation(delegate, resumeMode), CancellableContinuation { - @Volatile // just in case -- we don't want an extra data race, even benign one - private var _context: CoroutineContext? = null // created on first need - - public override val context: CoroutineContext - get() = _context ?: (delegate.context + this).also { _context = it } - - override fun initCancellability() { - initParentJobInternal(delegate.context[Job]) - } - - override val onCancelMode: Int get() = ON_CANCEL_MAKE_CANCELLED - - override fun tryResume(value: T, idempotent: Any?): Any? { - val state = this.state - return when (state) { - is Incomplete -> { - val update: Any? = if (idempotent == null) value else - CompletedIdempotentResult(idempotent, value, state) - tryUpdateState(update) - state - } - is CompletedIdempotentResult -> { - if (state.idempotentResume === idempotent) { - check(state.result === value) { "Non-idempotent resume" } - state.token - } else - null - } - else -> null // cannot resume -- not active anymore - } - } - - override fun tryResumeWithException(exception: Throwable): Any? { - val state = this.state - return when (state) { - is Incomplete -> { - tryUpdateState(CompletedExceptionally(exception)) - state - } - else -> null // cannot resume -- not active anymore - } - } - - override fun completeResume(token: Any) { - completeUpdateState(token as Incomplete, state, resumeMode) - } - - override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle = - invokeOnCompletion(onCancelling = false, invokeImmediately = true, handler = handler) - - override fun CoroutineDispatcher.resumeUndispatched(value: T) { - val dc = delegate as? DispatchedContinuation - resumeImpl(value, if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode) - } - - override fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable) { - val dc = delegate as? DispatchedContinuation - resumeImpl(CompletedExceptionally(exception), if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode) - } - - @Suppress("UNCHECKED_CAST") - override fun getSuccessfulResult(state: Any?): T = - if (state is CompletedIdempotentResult) state.result as T else state as T - - override fun nameString(): String = - "CancellableContinuation(${delegate.toDebugString()})" - - // todo: This workaround for KT-21968, should be removed in the future - public override fun cancel(cause: Throwable?): Boolean = - super.cancel(cause) -} - -private class CompletedIdempotentResult( - val idempotentResume: Any?, - val result: Any?, - val token: JobSupport.Incomplete -) { - override fun toString(): String = "CompletedIdempotentResult[$result]" -} - diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt deleted file mode 100644 index dca6ee9b64..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletableDeferred.kt +++ /dev/null @@ -1,82 +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. - */ - -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - -package kotlinx.coroutines.experimental - -/** - * A [Deferred] that can be completed via public functions - * [complete], [completeExceptionally], and [cancel]. - * - * Completion functions return `false` when this deferred value is already complete or completing. - * - * An instance of completable deferred can be created by `CompletableDeferred()` function in _active_ state. - * - * All functions on this interface and on all interfaces derived from it are **thread-safe** and can - * be safely invoked from concurrent coroutines without external synchronization. - */ -public actual interface CompletableDeferred : Deferred { - /** - * Completes this deferred value with a given [value]. The result is `true` if this deferred was - * completed as a result of this invocation and `false` otherwise (if it was already completed). - * - * Repeated invocations of this function have no effect and always produce `false`. - */ - public actual fun complete(value: T): Boolean - - /** - * Completes this deferred value exceptionally with a given [exception]. The result is `true` if this deferred was - * completed as a result of this invocation and `false` otherwise (if it was already completed). - * - * Repeated invocations of this function have no effect and always produce `false`. - */ - public actual fun completeExceptionally(exception: Throwable): Boolean -} - -/** - * Creates a [CompletableDeferred] in an _active_ state. - * It is optionally a child of a [parent] job. - */ -@Suppress("FunctionName") -public actual fun CompletableDeferred(parent: Job? = null): CompletableDeferred = CompletableDeferredImpl(parent) - -/** - * Creates an already _completed_ [CompletableDeferred] with a given [value]. - */ -@Suppress("FunctionName") -public actual fun CompletableDeferred(value: T): CompletableDeferred = CompletableDeferredImpl(null).apply { complete(value) } - -/** - * Concrete implementation of [CompletableDeferred]. - */ -@Suppress("UNCHECKED_CAST") -private class CompletableDeferredImpl( - parent: Job? -) : JobSupport(true), CompletableDeferred { - init { initParentJobInternal(parent) } - override val onCancelMode: Int get() = ON_CANCEL_MAKE_COMPLETING - - override fun getCompleted(): T = getCompletedInternal() as T - override suspend fun await(): T = awaitInternal() as T - - override fun complete(value: T): Boolean = - makeCompleting(value) - - override fun completeExceptionally(exception: Throwable): Boolean = - makeCompleting(CompletedExceptionally(exception)) -} diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferred.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt similarity index 57% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferred.kt rename to js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt index c24c4a1642..82db551554 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCompletableDeferred.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt @@ -16,13 +16,17 @@ package kotlinx.coroutines.experimental -public expect interface CompletableDeferred : Deferred { - public fun complete(value: T): Boolean - public fun completeExceptionally(exception: Throwable): Boolean -} +import kotlinx.coroutines.experimental.internal.* -@Suppress("FunctionName", "EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect fun CompletableDeferred(parent: Job? = null): CompletableDeferred +internal actual abstract class CompletionHandlerNode : LinkedListNode() { + @Suppress("UnsafeCastFromDynamic") + actual inline val asHandler: CompletionHandler get() = asDynamic() + actual abstract fun invoke(cause: Throwable?) +} -@Suppress("FunctionName", "EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") -public expect fun CompletableDeferred(value: T): CompletableDeferred +internal actual fun CompletionHandler.invokeIt(cause: Throwable?) { + when(jsTypeOf(this)) { + "function" -> invoke(cause) + else -> (this as CompletionHandlerNode).invoke(cause) + } +} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt index 73358a9261..6d1cbd894c 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt @@ -19,23 +19,6 @@ package kotlinx.coroutines.experimental import kotlin.browser.* import kotlin.coroutines.experimental.* -/** - * A coroutine dispatcher that is not confined to any specific thread. - * It executes initial continuation of the coroutine _right here_ in the current call-frame - * and let the coroutine resume in whatever thread that is used by the corresponding suspending function, without - * mandating any specific threading policy. - * - * Note, that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption, - * but still want to execute it in the current call-frame until its first suspension, then you can use - * an optional [CoroutineStart] parameter in coroutine builders like [launch] and [async] setting it to the - * the value of [CoroutineStart.UNDISPATCHED]. - */ -public actual object Unconfined : CoroutineDispatcher() { - actual override fun isDispatchNeeded(context: CoroutineContext): Boolean = false - actual override fun dispatch(context: CoroutineContext, block: Runnable) { throw UnsupportedOperationException() } - override fun toString(): String = "Unconfined" -} - private external val navigator: dynamic private const val UNDEFINED = "undefined" @@ -58,11 +41,13 @@ public actual val DefaultDispatcher: CoroutineDispatcher = when { else -> NodeDispatcher() } +internal actual val DefaultDelay: Delay = DefaultDispatcher as Delay + /** * 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). */ -public fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext { +public actual fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext { val wp = if (parent == null) context else context + parent return if (context !== DefaultDispatcher && context[ContinuationInterceptor] == null) wp + DefaultDispatcher else wp diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt deleted file mode 100644 index b6e659ea54..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt +++ /dev/null @@ -1,89 +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.AbstractCoroutineContextElement -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.ContinuationInterceptor -import kotlin.coroutines.experimental.CoroutineContext - -/** - * Base class that shall be extended by all coroutine dispatcher implementations. - * - * The following standard implementations are provided by `kotlinx.coroutines`: - * * [Unconfined] -- starts coroutine execution in the current call-frame until the first suspension. - * On first suspension the coroutine builder function returns. - * 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. - */ -public actual abstract class CoroutineDispatcher actual constructor() : - AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { - /** - * Returns `true` if execution shall be dispatched onto another thread. - * The default behaviour for most dispatchers is to return `true`. - * - * UI dispatchers _should not_ override `isDispatchNeeded`, but leave a default implementation that - * returns `true`. To understand the rationale beyond this recommendation, consider the following code: - * - * ```kotlin - * fun asyncUpdateUI() = async(MainThread) { - * // do something here that updates something in UI - * } - * ``` - * - * When you invoke `asyncUpdateUI` in some background thread, it immediately continues to the next - * line, while UI update happens asynchronously in the UI thread. However, if you invoke - * it in the UI thread itself, it updates UI _synchronously_ if your `isDispatchNeeded` is - * overridden with a thread check. Checking if we are already in the UI thread seems more - * efficient (and it might indeed save a few CPU cycles), but this subtle and context-sensitive - * difference in behavior makes the resulting async code harder to debug. - * - * Basically, the choice here is between "JS-style" asynchronous approach (async actions - * are always postponed to be executed later in the even dispatch thread) and "C#-style" approach - * (async actions are executed in the invoker thread until the first suspension point). - * While, C# approach seems to be more efficient, it ends up with recommendations like - * "use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent - * and does not require programmers to think about whether they need to yield or not. - * - * However, coroutine builders like [launch] and [async] accept an optional [CoroutineStart] - * parameter that allows one to optionally choose C#-style [CoroutineStart.UNDISPATCHED] behaviour - * whenever it is needed for efficiency. - */ - public actual open fun isDispatchNeeded(context: CoroutineContext): Boolean = true - - /** - * Dispatches execution of a runnable [block] onto another thread in the given [context]. - */ - public actual abstract fun dispatch(context: CoroutineContext, block: Runnable) - - /** - * Returns continuation that wraps the original [continuation], thus intercepting all resumptions. - */ - public actual override fun interceptContinuation(continuation: Continuation): Continuation = - DispatchedContinuation(this, continuation) -} - -/** - * A runnable task for [CoroutineDispatcher.dispatch]. - */ -public actual interface Runnable { - public actual fun run() -} - diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt deleted file mode 100644 index 61743d0098..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt +++ /dev/null @@ -1,80 +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.AbstractCoroutineContextElement -import kotlin.coroutines.experimental.CoroutineContext - -/** - * 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; - * * exception is logged to console. - */ -public actual fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { - context[CoroutineExceptionHandler]?.let { - it.handleException(context, exception) - return - } - // ignore CancellationException (they are normal means to terminate a coroutine) - if (exception is CancellationException) return - // try cancel job in the context - context[Job]?.cancel(exception) - // log exception - console.error(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; - * * exception is logged to console. - * - * See [handleCoroutineException]. - */ -public actual interface CoroutineExceptionHandler : CoroutineContext.Element { - /** - * Key for [CoroutineExceptionHandler] instance in the coroutine context. - */ - public actual companion object Key : CoroutineContext.Key - - /** - * Handles uncaught [exception] in the given [context]. It is invoked - * if coroutine has an uncaught exception. See [handleCoroutineException]. - */ - public actual fun handleException(context: CoroutineContext, exception: Throwable) -} - -/** - * Creates new [CoroutineExceptionHandler] instance. - * @param handler a function which handles exception thrown by a coroutine - */ -@Suppress("FunctionName") -public actual inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler = - object: AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { - override fun handleException(context: CoroutineContext, exception: Throwable) = - handler.invoke(context, exception) - } \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonScheduled.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerImpl.kt similarity index 76% rename from common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonScheduled.kt rename to js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerImpl.kt index d9181d0a9b..d9ade6625d 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonScheduled.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerImpl.kt @@ -16,6 +16,9 @@ package kotlinx.coroutines.experimental -public expect suspend fun withTimeout(time: Int, block: suspend CoroutineScope.() -> T): T +import kotlin.coroutines.experimental.* -public expect suspend fun withTimeoutOrNull(time: Int, block: suspend CoroutineScope.() -> T): T? +internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { + // log exception + console.error(exception) +} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Debug.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Debug.kt index e2e8908186..546864c9fa 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Debug.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Debug.kt @@ -16,4 +16,17 @@ package kotlinx.coroutines.experimental +private var counter = 0 + +internal actual val Any.hexAddress: String + get() { + var result = this.asDynamic().__debug_counter + if (jsTypeOf(result) !== "number") { + result = ++counter + this.asDynamic().__debug_counter = result + + } + return (result as Int).toString() + } + internal actual val Any.classSimpleName: String get() = this::class.simpleName ?: "Unknown" diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt deleted file mode 100644 index 9e36e31da7..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt +++ /dev/null @@ -1,181 +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. - */ - -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - -package kotlinx.coroutines.experimental - -import kotlinx.coroutines.experimental.intrinsics.* -import kotlin.coroutines.experimental.* - -/** - * Deferred value is a non-blocking cancellable future. - * - * It is created with [async] coroutine builder or via constructor of [CompletableDeferred] class. - * It is in [active][isActive] state while the value is being computed. - * - * Deferred value has the following states: - * - * | **State** | [isActive] | [isCompleted] | [isCompletedExceptionally] | [isCancelled] | - * | --------------------------------------- | ---------- | ------------- | -------------------------- | ------------- | - * | _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` | - * | _Failed_ (final state) | `false` | `true` | `true` | `false` | - * - * Usually, a deferred value is created in _active_ state (it is created and started). - * However, [async] coroutine builder has an optional `start` parameter that creates a deferred value in _new_ state - * when this parameter is set to [CoroutineStart.LAZY]. - * Such a deferred can be be made _active_ by invoking [start], [join], or [await]. - * - * A deferred can be _cancelled_ at any time with [cancel] function that forces it to transition to - * _cancelling_ state immediately. Deferred that is not backed by a coroutine (see [CompletableDeferred]) and does not have - * [children] becomes _cancelled_ on [cancel] immediately. - * Otherwise, deferred becomes _cancelled_ when it finishes executing its code and - * when all its children [complete][isCompleted]. - * - * ``` - * wait children - * +-----+ start +--------+ complete +-------------+ finish +-----------+ - * | New | ---------------> | Active | ----------> | Completing | ---+-> | Resolved | - * +-----+ +--------+ +-------------+ | |(completed)| - * | | | | +-----------+ - * | cancel | cancel | cancel | - * V V | | +-----------+ - * +-----------+ finish +------------+ | +-> | Failed | - * | Cancelled | <--------- | Cancelling | <---------------+ |(completed)| - * |(completed)| +------------+ +-----------+ - * +-----------+ - * ``` - * - * A deferred value is a [Job]. A job in the - * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) - * of [async] builder represents the coroutine itself. - * A deferred value is active while the coroutine is working and 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 deferred value can have a _parent_ job. A deferred value with a parent is cancelled when its parent is - * cancelled or completes. Parent waits for all its [children] 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. - */ -public actual interface Deferred : Job { - /** - * Returns `true` if computation of this deferred value has _completed exceptionally_ -- it had - * either _failed_ with exception during computation or was [cancelled][cancel]. - * - * It implies that [isActive] is `false` and [isCompleted] is `true`. - */ - public actual val isCompletedExceptionally: Boolean - - /** - * Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete, - * returning the resulting value or throwing the corresponding exception if the deferred had completed exceptionally. - * - * This suspending function is cancellable. - * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function - * immediately resumes with [CancellationException]. - */ - public actual suspend fun await(): T - - /** - * Returns *completed* result or throws [IllegalStateException] if this deferred value has not - * [completed][isCompleted] yet. It throws the corresponding exception if this deferred has - * [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. See also [getCompletionExceptionOrNull]. - */ - public actual 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 actual fun getCompletionExceptionOrNull(): Throwable? -} - -/** - * 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 can be explicitly specified. - * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`. - * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) - * of the parent coroutine may be used, - * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. - * The parent job may be also explicitly specified using [parent] parameter. - * - * 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. - * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,, - * the resulting [Deferred] is created in _new_ state. It can be explicitly started with [start][Job.start] - * function and will be started implicitly on the first invocation of [join][Job.join] or [await][Deferred.await]. - * - * @param context context of the coroutine. The default value is [DefaultDispatcher]. - * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. - * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). - * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). - * @param block the coroutine code. - */ -public actual fun async( - context: CoroutineContext = DefaultDispatcher, - start: CoroutineStart = CoroutineStart.DEFAULT, - parent: Job? = null, - onCompletion: CompletionHandler? = null, - block: suspend CoroutineScope.() -> T -): Deferred { - val newContext = newCoroutineContext(context, parent) - val coroutine = if (start.isLazy) - LazyDeferredCoroutine(newContext, block) else - DeferredCoroutine(newContext, active = true) - if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) - coroutine.start(start, coroutine, block) - return coroutine -} - -@Suppress("UNCHECKED_CAST") -private open class DeferredCoroutine( - parentContext: CoroutineContext, - active: Boolean -) : AbstractCoroutine(parentContext, active), Deferred { - override fun getCompleted(): T = getCompletedInternal() as T - override suspend fun await(): T = awaitInternal() as T -} - -private class LazyDeferredCoroutine( - parentContext: CoroutineContext, - private val block: suspend CoroutineScope.() -> T -) : DeferredCoroutine(parentContext, active = false) { - override fun onStart() { - block.startCoroutineCancellable(this, this) - } -} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt deleted file mode 100644 index 10b7c18e90..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt +++ /dev/null @@ -1,76 +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.ContinuationInterceptor -import kotlin.coroutines.experimental.CoroutineContext - -/** - * This dispatcher _feature_ is implemented by [CoroutineDispatcher] implementations that natively support - * scheduled execution of tasks. - * - * Implementation of this interface affects operation of - * [delay][kotlinx.coroutines.experimental.delay] and [withTimeout] functions. - */ -public actual interface Delay { - /** - * Schedules resume of a specified [continuation] after a specified delay [time]. - * - * Continuation **must be scheduled** to resume even if it is already cancelled, because a cancellation is just - * an exception that the coroutine that used `delay` might wanted to catch and process. It might - * need to close some resources in its `finally` blocks, for example. - * - * This implementation is supposed to use dispatcher's native ability for scheduled execution in its thread(s). - * In order to avoid an extra delay of execution, the following code shall be used to resume this - * [continuation] when the code is already executing in the appropriate dispatcher: - * - * ```kotlin - * with(continuation) { resumeUndispatched(Unit) } - * ``` - */ - fun scheduleResumeAfterDelay(time: Int, continuation: CancellableContinuation) - - /** - * Schedules invocation of a specified [block] after a specified delay [time]. - * The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] of this invocation - * request if it is not needed anymore. - */ - fun invokeOnTimeout(time: Int, block: Runnable): DisposableHandle -} - -/** - * Delays coroutine for a given time without blocking and resumes it after a specified time. - * This suspending function is cancellable. - * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function - * immediately resumes with [CancellationException]. - * - * This function delegates to [Delay.scheduleResumeAfterDelay] if the context [CoroutineDispatcher] - * implements [Delay] interface, otherwise it resumes using a built-in scheduler. - * - * @param time time in milliseconds. - */ -public actual suspend fun delay(time: Int) { - kotlin.require(time >= 0) { "Delay time $time cannot be negative" } - if (time <= 0) return // don't delay - return suspendCancellableCoroutine sc@ { cont: CancellableContinuation -> - cont.context.delay.scheduleResumeAfterDelay(time, cont) - } -} - -/** Returns [Delay] implementation of the given context */ -internal val CoroutineContext.delay: Delay get() = - get(ContinuationInterceptor) as? Delay ?: (DefaultDispatcher as Delay) diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt index 0d95eba792..37a04377aa 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt @@ -24,6 +24,12 @@ public actual class CompletionHandlerException public actual constructor( public override val cause: Throwable ) : RuntimeException(message.withCause(cause)) +/** + * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending. + * It indicates _normal_ cancellation of a coroutine. + * **It is not printed to console/log by default uncaught exception handler**. + * (see [handleCoroutineException]). + */ public actual open class CancellationException actual constructor(message: String) : IllegalStateException(message) /** @@ -47,26 +53,6 @@ public actual class JobCancellationException public actual constructor( (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0) } -/** - * This exception is thrown by [withTimeout] to indicate timeout. - */ -@Suppress("DEPRECATION") -public actual class TimeoutCancellationException internal constructor( - message: String, - internal val coroutine: Job? -) : CancellationException(message) { - /** - * Creates timeout exception with a given message. - */ - public actual constructor(message: String) : this(message, null) -} - -@Suppress("FunctionName") -internal fun TimeoutCancellationException( - time: Int, - coroutine: Job -) : TimeoutCancellationException = TimeoutCancellationException("Timed out waiting for $time", coroutine) - internal actual class DispatchException actual constructor(message: String, cause: Throwable) : RuntimeException(message.withCause(cause)) @Suppress("FunctionName") @@ -75,3 +61,6 @@ internal fun IllegalStateException(message: String, cause: Throwable?) = private fun String.withCause(cause: Throwable?) = if (cause == null) this else "$this; caused by $cause" + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) { /* empty */ } \ No newline at end of file diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt index 53cadc1fea..10e072f975 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt @@ -16,6 +16,7 @@ package kotlinx.coroutines.experimental +import kotlinx.coroutines.experimental.timeunit.TimeUnit import kotlin.coroutines.experimental.* import org.w3c.dom.* @@ -24,12 +25,12 @@ internal class NodeDispatcher : CoroutineDispatcher(), Delay { setTimeout({ block.run() }, 0) } - override fun scheduleResumeAfterDelay(time: Int, continuation: CancellableContinuation) { - setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.coerceAtLeast(0)) + override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation) { + setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) } - override fun invokeOnTimeout(time: Int, block: Runnable): DisposableHandle { - val handle = setTimeout({ block.run() }, time.coerceAtLeast(0)) + override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { + val handle = setTimeout({ block.run() }, time.toIntMillis(unit)) return object : DisposableHandle { override fun dispose() { clearTimeout(handle) @@ -60,12 +61,12 @@ internal class WindowDispatcher(private val window: Window) : CoroutineDispatche queue.enqueue(block) } - override fun scheduleResumeAfterDelay(time: Int, continuation: CancellableContinuation) { - window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.coerceAtLeast(0)) + override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation) { + window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) } - override fun invokeOnTimeout(time: Int, block: Runnable): DisposableHandle { - val handle = window.setTimeout({ block.run() }, time.coerceAtLeast(0)) + override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { + val handle = window.setTimeout({ block.run() }, time.toIntMillis(unit)) return object : DisposableHandle { override fun dispose() { window.clearTimeout(handle) @@ -106,6 +107,9 @@ internal abstract class MessageQueue : Queue() { } } +private fun Long.toIntMillis(unit: TimeUnit): Int = + unit.toMillis(this).coerceIn(0L, Int.MAX_VALUE.toLong()).toInt() + internal open class Queue { private var queue = arrayOfNulls(8) private var head = 0 diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt deleted file mode 100644 index 42de2d3a69..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt +++ /dev/null @@ -1,1113 +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. - */ - -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - -package kotlinx.coroutines.experimental - -import kotlinx.coroutines.experimental.internal.LinkedListHead -import kotlinx.coroutines.experimental.internal.LinkedListNode -import kotlin.coroutines.experimental.CoroutineContext -import kotlin.coroutines.experimental.buildSequence -import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn - -/** - * 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_ (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 (see `Job()` function) and does not have - * [children] 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 | - * +-----+ +--------+ +-------------+ +-----------+ - * | | | - * | cancel | cancel | cancel - * V V | - * +-----------+ finish +------------+ | - * | Cancelled | <--------- | Cancelling | <----------------+ - * |(completed)| +------------+ - * +-----------+ - * ``` - * - * A job in the - * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) - * 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 exceptionally. - * Parent job waits for all its children 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 actual interface Job : CoroutineContext.Element { - - // ------------ 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] to complete is still considered to be active if it - * was not cancelled. - */ - public actual 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] complete. - */ - public actual 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 actual val isCancelled: Boolean - - /** - * Returns [CancellationException] that signals the completion of this job. This function is - * used by [cancellable][suspendCancellableCoroutine] suspending functions. They throw exception - * returned by this function when they suspend in the context of this job and this job becomes _complete_. - * - * This function returns the original [cancel] cause of this job if that `cause` was an instance of - * [CancellationException]. Otherwise (if this job was cancelled with a cause of a different type, or - * was cancelled without a cause, or had completed normally), an instance of [JobCancellationException] is - * returned. The [JobCancellationException.cause] of the resulting [JobCancellationException] references - * the original cancellation cause that was passed to [cancel] function. - * - * This function throws [IllegalStateException] when invoked on a job that has not - * [completed][isCompleted] nor [cancelled][isCancelled] yet. - */ - public actual fun getCancellationException(): CancellationException - - // ------------ state update ------------ - - /** - * Starts coroutine related to this job (if any) if it was not started yet. - * The result `true` if this invocation actually started coroutine or `false` - * if it was already started or completed. - */ - public actual 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 actual fun cancel(cause: Throwable? = null): Boolean - - // ------------ parent-child ------------ - - /** - * Returns a sequence of this job's children. - * - * A job becomes a child of this job when it is constructed with this job in its - * [CoroutineContext] or using an explicit `parent` parameter. - * - * A parent-child relation has the following effect: - * - * * Cancellation of parent with [cancel] or its exceptional completion (failure) - * immediately cancels all its children. - * * Parent cannot complete until all its children are complete. Parent waits for all its children to - * complete in _completing_ or _cancelling_ state. - * * Uncaught exception in a child, by default, cancels parent. In particular, this applies to - * children created with [launch] coroutine builder. Note, that [async] and other future-like - * coroutine builders do not have uncaught exceptions by definition, since all their exceptions are - * caught and are encapsulated in their result. - */ - public actual val children: Sequence - - /** - * Attaches child job so that this job becomes its parent and - * returns a handle that should be used to detach it. - * - * A parent-child relation has the following effect: - * * Cancellation of parent with [cancel] or its exceptional completion (failure) - * immediately cancels all its children. - * * Parent cannot complete until all its children are complete. Parent waits for all its children to - * complete in _completing_ or _cancelling_ 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. - * - * @suppress This is an internal API. This method is too error prone for public API. - */ - @Deprecated(message = "Start child coroutine with 'parent' parameter", level = DeprecationLevel.WARNING) - public actual fun attachChild(child: Job): DisposableHandle - - // ------------ 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 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. - * - * There is [cancelAndJoin] function that combines an invocation of [cancel] and `join`. - */ - public actual suspend fun join() - - // ------------ low-level state-notification ------------ - - /** - * Registers handler that is **synchronously** invoked once on cancellation or completion of this job. - * When job is already cancelling or complete, then the handler is immediately invoked - * with a job's cancellation cause or `null` unless [invokeImmediately] is set to false. - * 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_. - * - * @param onCancelling when `true`, then the [handler] is invoked as soon as this job transitions to _cancelling_ state; - * when `false` then the [handler] is invoked only when it transitions to _completed_ state. - * @param invokeImmediately when `true` and this job is already in the desired state (depending on [onCancelling]), - * then the [handler] is immediately and synchronously invoked and [NonDisposableHandle] is returned; - * when `false` then [NonDisposableHandle] is returned, but the [handler] is not invoked. - * @param handler the handler. - */ - public actual fun invokeOnCompletion( - onCancelling: Boolean = false, - invokeImmediately: Boolean = true, - handler: CompletionHandler): DisposableHandle - - /** - * Key for [Job] instance in the coroutine context. - */ - public actual companion object Key : CoroutineContext.Key -} - -/** - * Creates a new job object in an _active_ state. - * It is optionally a child of a [parent] job. - */ -@Suppress("FunctionName") -public actual 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. - */ -public actual interface DisposableHandle { - /** - * Disposes the corresponding object, making it eligible for garbage collection. - * Repeated invocation of this function has no effect. - */ - public actual fun dispose() -} - -// -------------------- CoroutineContext extensions -------------------- - -/** - * Returns `true` when the [Job] of the coroutine in this context is still active - * (has not completed and was not cancelled yet). - * - * Check this property in long-running computation loops to support cancellation - * when [CoroutineScope.isActive] is not available: - * - * ``` - * while (coroutineContext.isActive) { - * // do some computation - * } - * ``` - * - * The `coroutineContext.isActive` expression is a shortcut for `coroutineContext[Job]?.isActive == true`. - * See [Job.isActive]. - */ -public actual val CoroutineContext.isActive: Boolean - get() = this[Job]?.isActive == true - -/** - * 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 actual 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 actual fun CoroutineContext.cancelChildren(cause: Throwable? = null) { - this[Job]?.cancelChildren(cause) -} - -// -------------------- Job extensions -------------------- - -/** - * Disposes a specified [handle] when this job is complete. - * - * This is a shortcut for the following code: - * ``` - * invokeOnCompletion { handle.dispose() } - * ``` - */ -public actual fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle = - invokeOnCompletion { handle.dispose() } - -/** - * 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 actual suspend fun Job.cancelAndJoin() { - cancel() - return join() -} - -/** - * Cancels all [children][Job.children] jobs of this coroutine with the given [cause] using [Job.cancel] - * for all of them. Unlike [Job.cancel] on this job as a whole, the state of this job itself is not affected. - */ -public actual fun Job.cancelChildren(cause: Throwable? = null) { - children.forEach { it.cancel(cause) } -} - -/** - * Suspends coroutine until all [children][Job.children] of this job are complete using - * [Job.join] for all of them. Unlike [Job.join] on this job as a whole, it does not wait until - * this job is complete. - */ -public actual suspend fun Job.joinChildren() { - children.forEach { it.join() } -} - -/** - * No-op implementation of [DisposableHandle]. - */ -public actual object NonDisposableHandle : DisposableHandle { - /** Does not do anything. */ - actual 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.** - */ -internal actual open class JobSupport actual constructor(active: Boolean) : Job { - public actual override val key: CoroutineContext.Key<*> get() = Job - - /** - * Returns current state of this job. - * @suppress **This is unstable API and it is subject to change.** - */ - // Note: use shared objects while we have no listeners - internal var state: Any? = if (active) EmptyActive else EmptyNew - private set - - private var parentHandle: DisposableHandle? = null - - // ------------ initialization ------------ - - /** - * Initializes parent job. - * It shall be invoked at most once after construction after all other initialization. - * @suppress **This is unstable API and it is subject to change.** - */ - internal actual fun initParentJobInternal(parent: Job?) { - check(parentHandle == null) { "Shall be invoked at most once" } - if (parent == null) { - parentHandle = NonDisposableHandle - return - } - parent.start() // make sure the parent is started - @Suppress("DEPRECATION") - val handle = parent.attachChild(this) - parentHandle = handle - // now check our state _after_ registering (see updateState order of actions) - if (isCompleted) { - handle.dispose() - parentHandle = NonDisposableHandle // release it just in case, to aid GC - } - } - - // ------------ state query ------------ - - public actual final override val isActive: Boolean get() { - val state = this.state - return state is Incomplete && state.isActive - } - - public actual final override val isCompleted: Boolean get() = state !is Incomplete - - public actual 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. - * @suppress **This is unstable API and it is subject to change.** - */ - internal fun updateState(proposedUpdate: Any?, mode: Int) { - val state = this.state as Incomplete // current state must be incomplete - val update = coerceProposedUpdate(state, proposedUpdate) - tryUpdateState(update) - completeUpdateState(state, update, mode) - } - - internal fun tryUpdateState(update: Any?) { - require(update !is Incomplete) // only incomplete -> completed transition is allowed - this.state = update - // Unregister from parent job - parentHandle?.let { - it.dispose() - parentHandle = NonDisposableHandle // release it just in case, to aid GC - } - } - - // when Job is in Cancelling state, it can only be promoted to Cancelled state, - // so if the proposed Update is not an appropriate Cancelled (preserving the cancellation cause), - // then the corresponding Cancelled state is constructed. - private fun coerceProposedUpdate(expect: Incomplete, proposedUpdate: Any?): Any? = - if (expect is Finishing && expect.cancelled != null && !isCorrespondinglyCancelled(expect.cancelled, proposedUpdate)) - createCancelled(expect.cancelled, proposedUpdate) else proposedUpdate - - private fun isCorrespondinglyCancelled(cancelled: Cancelled, proposedUpdate: Any?): Boolean { - if (proposedUpdate !is Cancelled) return false - // NOTE: equality comparison of causes is performed here by design, see equals of JobCancellationException - return proposedUpdate.cause == cancelled.cause || - proposedUpdate.cause is JobCancellationException && cancelled.cause == null - } - - private fun createCancelled(cancelled: Cancelled, proposedUpdate: Any?): Cancelled { - if (proposedUpdate !is CompletedExceptionally) return cancelled // not exception -- just use original cancelled - val exception = proposedUpdate.exception - if (cancelled.exception == exception) return cancelled // that is the cancelled we need already! - //cancelled.cause?.let { exception.addSuppressed(it) } - return Cancelled(this, exception) - } - - /** - * 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) { - val exceptionally = update as? CompletedExceptionally - // Do overridable processing before completion handlers - if (!expect.isCancelling) onCancellationInternal(exceptionally) // only notify when was not cancelling before - onCompletionInternal(update, mode) - // Invoke completion handlers - 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) - } - } - - private inline fun > notifyHandlers(list: NodeList, cause: Throwable?) { - var exception: Throwable? = null - list.forEach { 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>(this, cause) - - private fun notifyCancellation(list: NodeList, cause: Throwable?) = - notifyHandlers>(list, cause) - - public actual final override fun start(): Boolean { - val state = this.state - when (state) { - is Empty -> { // EMPTY_X state -- no completion handlers - if (state.isActive) return false // already active - this.state = EmptyActive - onStartInternal() - return true - } - is NodeList -> { // LIST -- a list of completion handlers (either new or active) - return state.makeActive().also { result -> - if (result) onStartInternal() - } - } - else -> return false // not a new state - } - } - - /** - * Override to provide the actual [start] action. - * @suppress **This is unstable API and it is subject to change.** - */ - internal actual open fun onStartInternal() {} - - public actual 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 - } - } - - // todo: non-final as a workaround for KT-21968, should be final in the future - public actual override fun invokeOnCompletion(onCancelling: Boolean, invokeImmediately: Boolean, handler: CompletionHandler) = - installNode(onCancelling, invokeImmediately, makeNode(handler, onCancelling)) - - private fun installNode( - onCancelling: Boolean, - invokeImmediately: Boolean, - node: JobNode<*> - ): DisposableHandle { - while (true) { - val state = this.state - when (state) { - is Empty -> { // EMPTY_X state -- no completion handlers - if (state.isActive) { - // move to SINGLE state - this.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) { - // cannot be in this state unless were support cancelling state - check(onCancelMode != ON_CANCEL_MAKE_CANCELLED) // cannot be in this state unless were support cancelling state - // installing cancellation handler on job that is being cancelled - if (invokeImmediately) node.invoke(state.cancelled.cause) - return NonDisposableHandle - } - list.addLast(node) - return node - } - } - else -> { // is complete - if (invokeImmediately) node.invoke((state as? CompletedExceptionally)?.cause) - return NonDisposableHandle - } - } - } - } - - private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode<*> { - val hasCancellingState = onCancelMode != ON_CANCEL_MAKE_CANCELLED - return if (onCancelling && hasCancellingState) - InvokeOnCancellation(this, handler) - else - InvokeOnCompletion(this, handler) - } - - - private fun promoteEmptyToNodeList(state: Empty) { - check(state === this.state) { "Expected empty state"} - // promote it to list in new state - this.state = NodeList(state.isActive) - } - - private fun promoteSingleToNodeList(state: JobNode<*>) { - check(state === this.state) { "Expected single state" } - // promote it to list (SINGLE+ state) - val list = NodeList(isActive = true) - list.addLast(state) - this.state = list - } - - public actual 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 { - if (state !is Incomplete) return false // not active anymore (complete) -- no need to wait - start() - return true // wait - } - - private suspend fun joinSuspend() = suspendCancellableCoroutine { cont -> - val handle = invokeOnCompletion { cont.resume(Unit) } - cont.invokeOnCompletion { handle.dispose() } - } - - internal fun removeNode(node: JobNode<*>) { - // remove logic depends on the state of the job - val state = this.state - when (state) { - is JobNode<*> -> { // SINGE/SINGLE+ state -- one completion handler - if (state !== node) return // a different job node --> we were already removed - // remove and revert back to empty state - this.state = EmptyActive - } - 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() - } - } - } - - protected open val onCancelMode: Int get() = ON_CANCEL_MAKE_CANCELLING - - public actual override fun cancel(cause: Throwable?): Boolean = when (onCancelMode) { - ON_CANCEL_MAKE_CANCELLED -> makeCancelled(cause) - ON_CANCEL_MAKE_CANCELLING -> makeCancelling(cause) - ON_CANCEL_MAKE_COMPLETING -> makeCompletingOnCancel(cause) - else -> error("Invalid onCancelMode $onCancelMode") - } - - // 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(cause: Throwable?) = - updateState(Cancelled(this, cause), mode = MODE_ATOMIC_DEFAULT) - - // transitions to Cancelled state - private fun makeCancelled(cause: Throwable?): Boolean { - if (state !is Incomplete) return false // quit if already complete - updateStateCancelled(cause) - return true - } - - // transitions to Cancelling state - private fun makeCancelling(cause: Throwable?): Boolean { - while (true) { - val state = this.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) - updateStateCancelled(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) { - makeCancellingList(state.list, cause) - return true - } else { - // cancelling a non-started coroutine makes it immediately cancelled - updateStateCancelled(cause) - return true - } - } - is Finishing -> { // Completing/Cancelling the job, may cancel - if (state.cancelled != null) return false // already cancelling - makeCancellingList(state.list, cause) - return true - } - else -> { // is inactive - return false - } - } - } - } - - // make expected state in cancelling - private fun makeCancellingList(list: NodeList, cause: Throwable?) { - val cancelled = Cancelled(this, cause) - state = Finishing(list, cancelled, false) - onFinishingInternal(cancelled) - onCancellationInternal(cancelled) - notifyCancellation(list, cause) - } - - private fun makeCompletingOnCancel(cause: Throwable?): Boolean = - makeCompleting(Cancelled(this, cause)) - - internal fun makeCompleting(proposedUpdate: Any?): Boolean = - when (makeCompletingInternal(proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) { - COMPLETING_ALREADY_COMPLETING -> false - else -> 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 actual fun makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean = - when (makeCompletingInternal(proposedUpdate, mode)) { - COMPLETING_COMPLETED -> true - COMPLETING_WAITING_CHILDREN -> false - else -> throw IllegalStateException("Job $this is already complete or completing, " + - "but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull) - } - - private fun makeCompletingInternal(proposedUpdate: Any?, mode: Int): Int { - loop@ while (true) { - val state = this.state - @Suppress("FoldInitializerAndIfToElvis") - if (state !is Incomplete) - return COMPLETING_ALREADY_COMPLETING - if (state is Finishing && state.completing) - return COMPLETING_ALREADY_COMPLETING - val child: Child? = firstChild(state) ?: // or else complete immediately w/o children - when { - state !is Finishing && hasOnFinishingHandler(proposedUpdate) -> null // unless it has onCompleting handler - else -> { - updateState(proposedUpdate, mode) - return COMPLETING_COMPLETED - } - } - val list = state.list ?: // must promote to list to correctly operate on child lists - when (state) { - is Empty -> { - promoteEmptyToNodeList(state) - continue@loop // retry - } - is JobNode<*> -> { - promoteSingleToNodeList(state) - continue@loop // retry - } - else -> error("Unexpected state with an empty list: $state") - } - // cancel all children in list on exceptional completion - if (proposedUpdate is CompletedExceptionally) - child?.cancelChildrenInternal(proposedUpdate.exception) - // switch to completing state - val completing = Finishing(list, (state as? Finishing)?.cancelled, true) - this.state = completing - if (state !is Finishing) onFinishingInternal(proposedUpdate) - if (child != null && tryWaitForChild(child, proposedUpdate)) - return COMPLETING_WAITING_CHILDREN - updateState(proposedUpdate, mode = MODE_ATOMIC_DEFAULT) - return COMPLETING_COMPLETED - } - } - - private tailrec fun Child.cancelChildrenInternal(cause: Throwable) { - childJob.cancel(JobCancellationException("Child job was cancelled because of parent failure", cause, childJob)) - nextChild()?.cancelChildrenInternal(cause) - } - - private val Any?.exceptionOrNull: Throwable? - get() = (this as? CompletedExceptionally)?.exception - - private fun firstChild(state: Incomplete) = - state as? Child ?: state.list?.nextChild() - - // return false when there is no more incomplete children to wait - private tailrec fun tryWaitForChild(child: Child, proposedUpdate: Any?): Boolean { - val handle = child.childJob.invokeOnCompletion(invokeImmediately = false) { - continueCompleting(child, proposedUpdate) - } - if (handle !== NonDisposableHandle) return true // child is not complete and we've started waiting for it - val nextChild = child.nextChild() ?: return false - return tryWaitForChild(nextChild, proposedUpdate) - } - - internal fun continueCompleting(lastChild: Child, proposedUpdate: Any?) { - val state = this.state - @Suppress("FoldInitializerAndIfToElvis") - 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() - // try wait for next child - if (waitChild != null && tryWaitForChild(waitChild, proposedUpdate)) return // waiting for next child - // no more children to wait -- update state - updateState(proposedUpdate, mode = MODE_ATOMIC_DEFAULT) - } - - private fun LinkedListNode.nextChild(): Child? { - var cur = this - while (cur.isRemoved) cur = cur.prev // rollback to prev non-removed (or list head) - while (true) { - cur = cur.next - if (cur is Child) return cur - if (cur is NodeList) return null // checked all -- no more children - } - } - - public actual final override val children: Sequence get() = buildSequence { - val state = this@JobSupport.state - when (state) { - is Child -> yield(state.childJob) - is Incomplete -> state.list?.let { list -> - list.forEach { yield(it.childJob) } - } - } - } - - @Suppress("OverridingDeprecatedMember") - public actual override fun attachChild(child: Job): DisposableHandle = - installNode(onCancelling = true, invokeImmediately = true, node = Child(this, child)) - - /** - * Override to process any exceptions that were encountered while invoking completion handlers - * installed via [invokeOnCompletion]. - * @suppress **This is unstable API and it is subject to change.** - */ - internal actual 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.** - */ - internal actual open fun onCancellationInternal(exceptionally: CompletedExceptionally?) {} - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal actual open fun hasOnFinishingHandler(update: Any?) = false - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal actual open fun onFinishingInternal(update: Any?) {} - - /** - * Override for 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.** - */ - internal actual open fun onCompletionInternal(state: Any?, mode: Int) {} - - // for nicer debugging - public override fun toString(): String = - "${nameString()}{${stateString()}}" - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal actual open fun nameString(): String = classSimpleName - - 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, - val cancelled: Cancelled?, /* != null when cancelling */ - 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( - override var isActive: Boolean - ) : LinkedListHead(), Incomplete { - override val list: NodeList get() = this - - fun makeActive(): Boolean { - if (isActive) return false - isActive = true - return true - } - - override fun toString(): String = buildString { - append("List") - append(if (isActive) "{Active}" else "{New}") - append("[") - var first = true - this@NodeList.forEach> { node -> - if (first) first = false else append(", ") - append(node) - } - append("]") - } - } - - /* - * ================================================================================================= - * 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 actual val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally - - public actual fun getCompletionExceptionOrNull(): Throwable? { - val state = this.state - check(state !is Incomplete) { "This job has not completed yet" } - return state.exceptionOrNull - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal fun getCompletedInternal(): Any? { - val state = this.state - check(state !is Incomplete) { "This job has not completed yet" } - if (state is CompletedExceptionally) throw state.exception - return state - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal suspend fun awaitInternal(): Any? { - val state = this.state - if (state !is Incomplete) { - // already complete -- just return result - if (state is CompletedExceptionally) throw state.exception - return state - } - start() - return awaitSuspend() // slow-path - } - - private suspend fun awaitSuspend(): Any? = suspendCancellableCoroutine { cont -> - val handle = invokeOnCompletion { - val state = this.state - check(state !is Incomplete) { "State should be complete "} - if (state is CompletedExceptionally) - cont.resumeWithException(state.exception) - else - cont.resume(state) - } - cont.invokeOnCompletion { handle.dispose() } - } -} - -internal const val ON_CANCEL_MAKE_CANCELLED = 0 -internal const val ON_CANCEL_MAKE_CANCELLING = 1 -internal const val ON_CANCEL_MAKE_COMPLETING = 2 - -private const val COMPLETING_ALREADY_COMPLETING = 0 -private const val COMPLETING_COMPLETED = 1 -private const val COMPLETING_WAITING_CHILDREN = 2 - -@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 { initParentJobInternal(parent) } - override val onCancelMode: Int get() = ON_CANCEL_MAKE_COMPLETING -} - -// -------- invokeOnCompletion nodes - -internal abstract class JobNode( - val job: J -) : LinkedListNode(), DisposableHandle, 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) - abstract fun invoke(reason: Throwable?) // CompletionHandler -- invoked on completion -} - -private class InvokeOnCompletion( - job: Job, - private val handler: CompletionHandler -) : JobNode(job) { - override fun invoke(reason: Throwable?) = handler.invoke(reason) - override fun toString() = "InvokeOnCompletion" -} - -// -------- 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(job: J) : JobNode(job) - -private class InvokeOnCancellation( - job: Job, - private val handler: CompletionHandler -) : JobCancellationNode(job) { - // delegate handler shall be invoked at most once, so here is an additional flag - private var invoked = false - override fun invoke(reason: Throwable?) { - if (invoked) return - invoked = true - handler.invoke(reason) - } - override fun toString() = "InvokeOnCancellation" -} - -internal class Child( - parent: JobSupport, - val childJob: Job -) : JobCancellationNode(parent) { - override fun invoke(reason: Throwable?) { - // Always materialize the actual instance of parent's completion exception and cancel child with it - childJob.cancel(job.getCancellationException()) - } - override fun toString(): String = "Child[$childJob]" -} - diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt deleted file mode 100644 index 09a8789ebb..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt +++ /dev/null @@ -1,68 +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.NonCancellable.isActive -import kotlin.coroutines.experimental.AbstractCoroutineContextElement - -/** - * A non-cancelable job that is always [active][isActive]. It is designed to be used with [run] builder - * to prevent cancellation of code blocks that need to run without cancellation. - * - * Use it like this: - * ``` - * run(NonCancellable) { - * // this code will not be cancelled - * } - * ``` - */ -public actual object NonCancellable : AbstractCoroutineContextElement(Job), Job { - /** Always returns `true`. */ - actual override val isActive: Boolean get() = true - - /** Always returns `false`. */ - actual override val isCompleted: Boolean get() = false - - /** Always returns `false`. */ - actual override val isCancelled: Boolean get() = false - - /** Always returns `false`. */ - actual override fun start(): Boolean = false - - /** Always throws [UnsupportedOperationException]. */ - actual suspend override fun join() { - throw UnsupportedOperationException("This job is always active") - } - - /** Always throws [IllegalStateException]. */ - actual override fun getCancellationException(): CancellationException = throw IllegalStateException("This job is always active") - - /** Always returns [NonDisposableHandle]. */ - actual override fun invokeOnCompletion(onCancelling: Boolean, invokeImmediately: Boolean, handler: CompletionHandler): DisposableHandle = - NonDisposableHandle - - /** Always returns `false`. */ - actual override fun cancel(cause: Throwable?): Boolean = false - - /** Always returns [emptySequence]. */ - actual override val children: Sequence - get() = emptySequence() - - /** Always returns [NonDisposableHandle] and does not do anything. */ - @Suppress("OverridingDeprecatedMember") - actual override fun attachChild(child: Job): DisposableHandle = NonDisposableHandle -} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.kt new file mode 100644 index 0000000000..e9e1b7d5c7 --- /dev/null +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Runnable.kt @@ -0,0 +1,35 @@ +/* + * 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 + +/** + * A runnable task for [CoroutineDispatcher.dispatch]. + */ +public actual interface Runnable { + public actual fun run() +} + +/** + * Creates [Runnable] task instance. + */ +@Suppress("FunctionName") +public actual inline fun Runnable(crossinline block: () -> Unit): Runnable = + object : Runnable { + override fun run() { + block() + } + } diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt deleted file mode 100644 index a884a52f33..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt +++ /dev/null @@ -1,125 +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.intrinsics.* -import kotlin.coroutines.experimental.* -import kotlin.coroutines.experimental.intrinsics.* - -/** - * 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]. - * - * 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 in milliseconds. - */ -public actual suspend fun withTimeout(time: Int, 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 -> - setupTimeout(TimeoutCoroutine(time, cont), block) - } -} - -private fun setupTimeout( - coroutine: TimeoutCoroutine, - 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)) - // restart block using new coroutine with new job, - // however start it as undispatched coroutine, because we are already in the proper context - return coroutine.startUndispatchedOrReturn(coroutine, block) -} - -private open class TimeoutCoroutine( - val time: Int, - val cont: Continuation -) : AbstractCoroutine(cont.context, active = true), Runnable, Continuation { - override val defaultResumeMode: Int get() = MODE_DIRECT - - @Suppress("LeakingThis") - override fun run() { - cancel(TimeoutCancellationException(time, this)) - } - - @Suppress("UNCHECKED_CAST") - internal override fun onCompletionInternal(state: Any?, mode: Int) { - if (state is CompletedExceptionally) - cont.resumeWithExceptionMode(state.exception, mode) - else - cont.resumeMode(state as T, mode) - } - - override fun toString(): String = - "TimeoutCoroutine($time)" -} - -/** - * 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]. - * - * 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 in milliseconds. - */ -public actual suspend fun withTimeoutOrNull(time: Int, block: suspend CoroutineScope.() -> T): T? { - require(time >= 0) { "Timeout time $time cannot be negative" } - if (time <= 0L) return null - return suspendCoroutineOrReturn { cont: Continuation -> - setupTimeout(TimeoutOrNullCoroutine(time, cont), block) - } -} - -private class TimeoutOrNullCoroutine( - time: Int, - cont: Continuation -) : TimeoutCoroutine(time, cont) { - @Suppress("UNCHECKED_CAST") - internal override fun onCompletionInternal(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) - } - - override fun toString(): String = - "TimeoutOrNullCoroutine($time)" -} - diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt deleted file mode 100644 index 833a728d66..0000000000 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt +++ /dev/null @@ -1,43 +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.CoroutineContext -import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED -import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn - -/** - * Yields a thread (or thread pool) of the current coroutine dispatcher to other coroutines to run. - * If the coroutine dispatcher does not have its own thread pool (like [Unconfined] dispatcher) then this - * function does nothing, but checks if the coroutine [Job] was completed. - * This suspending function is cancellable. - * If the [Job] of the current coroutine is cancelled or completed when this suspending function is invoked or while - * this function is waiting for dispatching, it resumes with [CancellationException]. - */ -public actual suspend fun yield(): Unit = suspendCoroutineOrReturn sc@ { cont -> - val context = cont.context - context.checkCompletion() - if (cont !is DispatchedContinuation) 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/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt index 54aa2b3209..453bf84f02 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt @@ -18,62 +18,121 @@ package kotlinx.coroutines.experimental.internal private typealias Node = LinkedListNode -/** - * @suppress **This is unstable API and it is subject to change.** - */ +/** @suppress **This is unstable API and it is subject to change.** */ +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // :TODO: Remove when fixed: https://youtrack.jetbrains.com/issue/KT-23703 +public actual typealias LockFreeLinkedListNode = LinkedListNode + +/** @suppress **This is unstable API and it is subject to change.** */ +public actual typealias LockFreeLinkedListHead = LinkedListHead + +/** @suppress **This is unstable API and it is subject to change.** */ public open class LinkedListNode { - public var next = this - private set - public var prev = this - private set - public var isRemoved: Boolean = false - private set - public val isFresh: Boolean = next === this + @PublishedApi internal var _next = this + @PublishedApi internal var _prev = this + @PublishedApi internal var _removed: Boolean = false + + public inline val nextNode get() = _next + public inline val prevNode get() = _prev + public inline val isRemoved get() = _removed public fun addLast(node: Node) { - val prev = this.prev - node.next = this - node.prev = prev - prev.next = node - this.prev = node + val prev = this._prev + node._next = this + node._prev = prev + prev._next = node + this._prev = node } public open fun remove(): Boolean { - if (isRemoved) return false - val prev = this.prev - val next = this.next - prev.next = next - next.prev = prev - isRemoved = true + if (_removed) return false + val prev = this._prev + val next = this._next + prev._next = next + next._prev = prev + _removed = true + return true + } + + public fun addOneIfEmpty(node: Node): Boolean { + if (_next !== this) return false + addLast(node) + return true + } + + public inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean { + if (!condition()) return false + addLast(node) + return true + } + + public inline fun addLastIfPrev(node: Node, predicate: (Node) -> Boolean): Boolean { + if (!predicate(_prev)) return false + addLast(node) + return true + } + + public inline fun addLastIfPrevAndIf( + node: Node, + predicate: (Node) -> Boolean, // prev node predicate + crossinline condition: () -> Boolean // atomically checked condition + ): Boolean { + if (!predicate(_prev)) return false + if (!condition()) return false + addLast(node) return true } + + public fun removeFirstOrNull(): Node? { + val next = _next + if (next === this) return null + check(next.remove()) { "Should remove" } + return next + } + + public inline fun removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? { + val next = _next + if (next === this) return null + if (next !is T) return null + if (predicate(next)) return next + check(next.remove()) { "Should remove" } + return next + } } -/** - * @suppress **This is unstable API and it is subject to change.** - */ +/** @suppress **This is unstable API and it is subject to change.** */ +public actual open class AddLastDesc actual constructor( + actual val queue: Node, + actual val node: T +) : AbstractAtomicDesc() { + protected override val affectedNode: Node get() = queue._prev + protected actual override fun onPrepare(affected: Node, next: Node): Any? = null + protected override fun onComplete() = queue.addLast(node) +} + +/** @suppress **This is unstable API and it is subject to change.** */ +public actual abstract class AbstractAtomicDesc : AtomicDesc() { + protected abstract val affectedNode: Node + protected actual abstract fun onPrepare(affected: Node, next: Node): Any? + protected abstract fun onComplete() + actual final override fun prepare(op: AtomicOp<*>): Any? = onPrepare(affectedNode, affectedNode._next) + actual final override fun complete(op: AtomicOp<*>, failure: Any?) = onComplete() +} + +/** @suppress **This is unstable API and it is subject to change.** */ public open class LinkedListHead : LinkedListNode() { - public val isEmpty get() = next === this + public val isEmpty get() = _next === this /** * Iterates over all elements in this list of a specified type. */ public inline fun forEach(block: (T) -> Unit) { - var cur: Node = next + var cur: Node = _next while (cur != this) { if (cur is T) block(cur) - cur = cur.next + cur = cur._next } } // just a defensive programming -- makes sure that list head sentinel is never removed public final override fun remove() = throw UnsupportedOperationException() - - fun removeFirstOrNull(): Node? { - val node = next - if (node === this) return null - node.remove() - return node - } } - diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.kt new file mode 100644 index 0000000000..5bf8eae0c1 --- /dev/null +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/timeunit/TimeUnit.kt @@ -0,0 +1,41 @@ +/* + * 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.timeunit + +/** + * Time unit. This class is provided for better JVM interoperability. + * **It is available for common code, but its use in common code is not recommended.** + */ +@Deprecated("Using this TimeUnit enum in JS code is not recommended, use functions without it") +public actual enum class TimeUnit { + /** Milliseconds. */ + MILLISECONDS, + /** Seconds. */ + SECONDS; + + /** + * Converts time in this time unit to milliseconds. + */ + public actual fun toMillis(time: Long): Long = when(this) { + MILLISECONDS -> time + SECONDS -> when { + time >= Long.MAX_VALUE / 1000L -> Long.MAX_VALUE + time <= Long.MIN_VALUE / 1000L -> Long.MIN_VALUE + else -> time * 1000L + } + } +} \ No newline at end of file diff --git a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt index fccde08b23..7a3da049e8 100644 --- a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt +++ b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt @@ -14,9 +14,6 @@ * limitations under the License. */ -// :todo: Remove after transition to Kotlin 1.2.30+ -@file:Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - package kotlinx.coroutines.experimental import kotlin.js.* @@ -34,6 +31,7 @@ public actual open class TestBase actual constructor() { * complete successfully even if this exception is consumed somewhere in the test. */ public actual fun error(message: Any, cause: Throwable? = null): Nothing { + if (cause != null) console.log(cause) val exception = IllegalStateException( if (cause == null) message.toString() else "$message; caused by $cause") if (error == null) error = exception From 31550f1ddb9f96f4b59fbeb937be1703ed133d5f Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 11 Apr 2018 13:57:24 +0300 Subject: [PATCH 18/61] Gradle 4.6 --- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 54708 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 16e5ff92ee..a062258569 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ junit_version = 4.12 atomicFU_version = 0.10.0-alpha html_version = 0.6.8 lincheck_version=1.9 -dokka_version = 0.9.16-dev-mpp-hacks-1 +dokka_version = 0.9.16-rdev-2-mpp-hacks bintray_version = 1.7.3 gradle_node_version = 1.2.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7a3265ee94c0ab25cf079ac8ccdf87f41d455d42..f6b961fd5a86aa5fbfe90f707c3138408be7c718 100644 GIT binary patch delta 15814 zcmZ9zbC6}r6D{1fZQHhO+qUg9ZS%Bk+n#CL=1kl6bWiu2d*AoPFYY^kRn%U&t9C?I z*2$GCTWi3(D#4SuA!rmwUz7QDptJ7W?%Y5@lJJ^Z37rvJua-cEAVEO5kw8GGfUIeR zz_=L%K%SPqH@YUqClz8A2ks`C&5|27Tq&&MvYv>ZC{2eQvy45xWSA{mdFYZtrb1^_ z%*zGyuMzH}5oj0K+DSd8f`D7=SMlW=g>s-QRWcK?pH;!s=d<_o=dhIC2~B_nm`%8i&h{0-e1qO^IT z8tj;A!zK<`{|oQ2YISi&tmc;XGHm#gvD2bTt)k{)%&V>~Npp0WqW+C6>cM>>==jCJen&TS66JYLJXJN~`vU-n3X6~ojkti!u9Tjvh9XYd2k!&UAcjHtf*=Kwo_Ci{(ckRM~2kA$4HBnEuSSMz5dC8kWoH8y#%kmSD4R{0^-rm+|Uc%!W| z(1Nek<661DHDigQkuHt-;bf!>enzs#7ZDj!c`>CD;w@;3bF2se+f)O0_))jJis4+9 zfiUAOu^?NxYM86^`dho`Vngpe0ph8-9!jYcuatVxmF7vfkwAAY8D zQ2Pr#U~+M#kot*j1n58t4f)<^Jir@JP7;a%^s9Q#H`H>0vc@E+bXwk3atcUrg*5&p zzxSRN(r_0a;w*W(Gbg7(c$ZaXuQ%&8%<^4oYa^4}Q%gX=`|iUwo#wS3AQN?d4L>fh zmbUxCMio+{!Q&}dw_~>KvhcR-ZIqT`Q_TmNRFX|ycCB$+i*j$WbZoQJth!ap)bxT- zr-OoKg+)c-pd9~|VIIb@&KiL7fk()V7^)G50*T*`M}LfnICod@iu9=#MMByNxfk^}}96GD~_Ooeh%4AN=S~4+5k8gHaT#JpT-{ z_c*tH3zNJVldCJKF}OUS*(%=|>BwP*_UB;f%ov5pAbgOR<>l+`f^=as>8KN0HGx06 zK!VzDA=L4(55DBkBVrXoSzr#`HXlba_j|Wg5~q)L2wZ$y$Pn z$mb=7cM9A?iC;x<#hgfcqfQ~dM(6CMlgN9pcTzQscN){RT(A%Ku1$p0xRA8lIxu%@ zM+8lY19D>>!D4+6u!e9*F>08^UmNQFOv|V9I4f(_aDhyNdlwj|2ybgjiI!7f5^L^7 z?Pp^}M6L_pk){g;k8UZ;r<-DAHY1B!i1sVo}PN5V9QP5&!XRfFXfVewfJc&SC6yv>HMgU4Pv}+-+gg3}O9BDmQc;=ncf%Ck? z0p`6Tp2pytTKViw{Opust|T+JKi=VJ#emHlhk*-U&?@ADK{y2@4Do1P!0)guqYmiw79dx==z_Mfsdwp_>F>-fX0C6rc*Mh_7RmG*-6n?k>%wTv14Q3M;o?r5JMf-t+bytKFw>kcqc( z@K+w!s%8Wi)A*%BUDdI>Wt~h)LTj>i41G-1U7+si(KVY#xsh%)Z%GycfaJMTa70a~m zQrVft8KbN66q46EE89p$OCD-luwjsjSRh9CS9Hx_3A6S3^Pz^M%|>(WYKON&3RynO zH`50(pJx&{RCcfFUcQpM7+9?hgnPi_>q4-65X3yweibo$ zCMN62m_R=3W2a@P>Gn!8_V041%X8{fkr!J!;rcy3Z-->NwWpA9^ViZ|wlPI5FR*Dn{+KHaU}I6|3KJ(QYP1|!6?BC4 zC9F`nv)fIr#l3(^hleUx1rb666~>7ENz!D)EtQAWy$!kKC`vrdF)@GdMW3#3UbVUG zPh2-*h9&hzt;Q!f0&VoY>At?_e;Sr0=R#tLeou8Q_q!fw52}WD94%L$+kP-B-ek=5%Br@8C(Qa z9#2ETPR6`CgTip6HG;y5Gc!|NRfBadsjBmd4|^qrks)Fs6eir)f(5t{7DRq*a&AtaQSwe98hQl(6|!3O{x`u>gdq* z<&+5+#k8+~RaZQ2KuG6b!6IhHlO6GdVyPuM$UK-BDa1_e<9(xJizBp#4Q;=^_QSj= zV$?V9oUzuz%94cAed|t^WahwxM~H2DM7-1Mypf1p!aw7pG~d>6rH_@|w#wvHMP^ed zuz1HkR^BD_2tDuyq?s09N%Z7tlg=jYiB}kXAtRzYjsO%p7@2lVjOv8e9|Vn#XX{sv z$W^3*`!j9FpNUfiCDo8IHmohq$gfas;o@=g2t^j+nPo14)rAGSe`Mw-Pfks9=GU>@ zEbyJW$rIH@NMspGPB!%!T*y;Qt4Cz_Sn7FJA-Ao4H@mw2mKSVWAh zZ3Qw?g+;(gGHe*;qXM!&gQ2Vn|3LlMOV!J{&VZzWdH|bJQ4;1b5IR{E_~j0?Xvb8p zN=I%}MQxy=bxYM}_-EKBJpU4s-e~(zz9r^A#D7rp@_X>bS%MK0*#Ed0&jw!e&ILYi zx4bm`4f}{9c*vHy!;7qTY;1!%`{NTyjSS?m@}LHzmB>+?<)${PdH_X74vr8;-;}s#4+&~Sjpsta;WHul zp+;K_$CYMyWrrOOo_-S?*D)ssz1E4fRgcf)-+kHDRZ^Uqt&BUB$K8zVyURTR=+NKo zG{Zd%KTo%^kn$zmps&Fc;or-vg1|cn6Fp=!ZAN$)atss_O2$DV#%?ImdUY%EJRSsd z&;g2i91jQ!LC>t?k}bBHG>Q^P30|gF;oERZrV0ZzyC*G+S3Igty#q zZObH^r8=W>+>@&&Mwc1HU6_U8kEHcRjsdS)!7)1v{igxgXZWFbr*h;WB7y*X!0?G{w%P$9*SHh!nSt+1$RIN^&%opc1&G` zNp!tOw3b*&cR(OMFqpddi8BcDvVN4~eBt0(m_b=# z7}Z5Wcff`9g5~uJ-;J$7`Xia4lhYqTtAOsvnq8EP4C+7{0YMm;dnjv$r3OHu(Czl@ z1gn!-c9&*suUIW~89L5mVZ~lV8e&yGXSZ(sbxJ7}%X1Tu2%nG`%On1W&el66f2d*t ztt?0lZSII+|B+5fHSrh9u)8nfK5Om><6wBzm%;rW=##PhJ>b8m{(C!YwICP>$SMQ~ zh}7TP%GnLbl|~Fq38n^YIW38x2woU$Lq$fkBC2TU$rW~~atDgis6t4H#bAJ{Rrfom z#BWSC(Qj3~Vt$e`_hLjt53>WpI%hYvP!vgIXeMVbU#GKp5AG^%ZiGQ94`{#%M-a4@ zlLrMox7V5)1}OKxZ~~%@B(K#j@|*If<&D}p$o1Adu@)_?;&XUUWRjQ zjETsOrbrI9XkOEKMb*Dk{QGZXyj8& z)IUyxS|ujRY^VSdq!4P|qyI>LX&{7*29$0u+fpX<J z?qe2ZQ5EmPOks{}Iu}UQB)^UG#6nUTdtJySdwq(t1V}9@avMFzAdj!aDaA%Wn^&-e z{X3%CmiiHO1$%j~SR=DcOT&1hGs}8nQX87(Xzk>d1Zi#RnCCsm`M?jvZ3(iMFgvFa zPGT}qVG@14svwDKsW14_3V^Kue%g4jhVvLj;<#OtUAvAaDcJOH`8_VLQ(v$i(odvk zF@Nl}!F*0U*Q{!XJuKZrtst=f`kAu<@AscjARvvXARvN&xg`o9OBfO0>>p<7tB>m{ z%;N_djgCs(;v&}$dI$T!fvBTAlt+2e7-oY~KuhVsHzd4{*ybnFd>Y5+YA1dw*;(-#^Ps`r!6Z_6`;|$oVJ^ zUVt?(*xN_!+U8^vW!zG!a>bR*H!H4bws02dq}#zRci>y~3ONDjn!@ym6>XmCq?8kj z$2K_)cPcZ}?b72jj44%4^-wyG$%^X{T(Kp`qI4w)(3@OCDAY{;@>z20;*0h0c?u|% zQ&$%}L0)ajvJ`KrBUf8#LS;g>_R+t+W}0oCr7E!2F!^m$_M2t5YIZ!u77m_vyK;2% zcItF35wV54E97+{JQcEoOGT&rw2B@+P}}JEv=g4O9~DmQK*kv1sj};?ArG6GWOFkgFP25jA$* zRr}hDzeBRlA*@)RV{E0DL30$~IW&~EMr%9m8q{l9=NE9;wgQwX{xj?YFZ@9 z^yr!VOw3yjD;f@=>^0+!J(}*J+7WII=$p%I?3P5X66||U zXw|7SZW8M&U#J*a);Sd&LXc!O=~fyf#f@~8kb(eQSQ1D^#(B?^?imIj@si%mGsHGe z&Jgf5djnD}%39`bl2@Z#X%i-Wh{q@bNDjmaGVV~3Uy(?d|#IWQ+$-Ya$3|I7;8rnp}4Ugb(GDPrp`4 zse^2~533%#avIaZ=lm8uhd@T(v`%rBife};dXCK)9kx*5guARvIiok_>9a8GGDq9! z31?#QyjBIKQz}tXZJEZ&WM8P?<~iBED98tZPXQvQE#RCUNfnlL?*!_8o7)Lw<9iAB zqv$hZaHQTaVB-XJOE7ES@b`qGuVMR{2frw)Xh?YwaqJE=g2q|3nd{FyY7p|423(Ia_4ktaA{Q85X|PK_cntl=Nm|L8i!jP(e> z*vvsKCKfjR?W!3?!p)Zr*|o-NZ09|j!I%F~<|NaQa$%bvsW~#TEk-yYa9!Ezj$Zdn zT0kSB*y#u3Yswn(af|!fEv3Q|Hs~^gQtEPr^0BA(6U8b_J{phb$i|__6_vv8l|V$~ zQaYL^3UVK%9xYOpDWOo0U<7YpHv$Z>ny6I^nTz|LcQU>H5MVvp|DKEMZ!EIA6QaK) zB!>i`5Qr-V*YSaJ({?Gp(Vsq_Lk9(S!SQAe2&ZjN&YZ#C{dxWfA?CyD@4ftX(` za!{(1&r+<)CMfK4CPCPt{%;e}<<@t1BiO^9Xry;#=1N|ezQ+*X4F$**6I4|=49E-0 zpD@_+9up_tm+q5OfoABS6C_^)B1pw%xsgnEWxA?40~bp=zw`{(uVV~Fk*iG&&J>7l z3FY$wRO!I9EfasxMH{1PJwB^VIu;fln`2{{kvmc6S;G$T=RydIMqe+&@FTBT^~NYUgwOzv->{g)sLoP& zp!UwQH`R=72VjP)WK}m0bPP}K%YbsDh8p>US)RU-WCuBItEZ`csM7m zWpL}SxY`xoVw^WT*wj4RyZQ#o${uf7j{DkZb{0<3FZ7CuNN%S5bXL@E7`pw`XwRU& zIxSPE`hF2gPG|vs-U%ADc2#1p3CcRrcPU2DWO%0iY|20gO*j>C@DlY1eqQ^NO1?_q zibJ=;`V)G`?S`a`lGl@-RUvOUT-4ax z>!|&0T`iW)YuZZdY3K*a;(5D`$QUlTWgNcFAR%~$%f=4?8`VC^su{q4n8iICi+#GF zKNtbnw>7{L7_tWuucULmhjjM&!`neUJZmc7&(z~PW)Gse4?>pF?D#x118kcdxg8#b zZB!-f!UT)0vP|qedzv+L)FHeu>JBxh*fGfijqkO|x4y_(irK{t9g_riih&G_^aW3% z15yv(FuON^7dvcDri-N=FqD0U!{qwKh^)EzB2!ZCnkb6CW*9QNuRb(@+v z0~SHQa{#4+BCix?LYoCO+%7sxPEl^NGHKF0FLs|R! z5+3ccCXpR%2qXha(UdMsfMUvKz7f3f5BUVgCY^(X^SbBvJGt&)+waA-H(4dw!Lq|P zJd`-BPw4cf80XkpgPM!fXVD(}#Q*i?(BCLkS$}hg>c7tX&wo2Q;E#DMK%M4>(~=5` zAh+wmu&XSMqt2w-Vk*^j3Rh(+6GK^XrJ6=k2w7T3&M*Y_nA955RK+c1?=1)#qy(kV zw?wgNMMar3l8l&)IP2qSULe2w6|wO5KSFGn2ozVUuJAMv;#U6q6E9>Y8 z3#!oHB~OfW;UeAqtpk_naAy z_kh`V;aI6f=Pvw+V+~Fjr`UkQawt$1FZ$xLbJjWJFxlz`u^9g5)h0)8#4)zJ%W~pi z;<4zYEF=MAkx$87c8o>otTWgZbBfQ&Omd_;fwPvKA)L-x;7PH`5_Ybk)m}8))I&w5 zddDo2pXg-oZT`n2z)0I|zMiqbB?6)4%PpPm`Y-#;7trP&o3|3Lzk4&F6n4co#Ny0x z{rwrT!L7&f(=x02k4E4*hr3ZjPJV{IUSTZ1XIu?U#^W2Ht9eW?=Srvz=x#aWItkH7 z>gne##X_e|AX9O)s7?VF{%jLQsUUmW$0o#L|$$tjcPB%%1vU*pG9lcjWJ5W|)t|w2yX#%VbXW zYhy?ST9ZwPR8B+OVncg)m&_f+Q`?VCJa6nLspq$0r(br&-Pzd|Chk!!Z$vw4+8r3D zQZ8-kz!BbNRw!EPvv)VG=y_c%7s+JfEHEqBWDFU)df{9oQBB&QP)YhI5rWK7?@d`bOsYP@%=HjY+u>6ly>q@1OSZj2bdJvko8x* z>UX?3L;PF^HHG$F_c`BL_u0P$?(;>%oG}Q-otP*dJKXP)666-PV+@&0?eTUJbJfw5 zkkWPPCI)lK9|hw&X-!n()~C8B0InBqoPA%<3{g7Xcd8+g@U3I7mL}_N=QzPw4spy1 zE1jN*sSzO!xljIURtqnr!0UC&3Rj!Xh?`D?s%FO=rwKNJIf`Mk5!Va79nNP)^%`9R za)+VD7s<@Eh>I+zYc}X3sx>`+xJNnwr0B0IY4T+@S3btBaTeSw7f|CuKnqvrEnYPN zj&y_-u9soWZ>Ek5KNIqCjsBU=7H^Z)fIjt-bjw=y!tg`}c~xx3=)MB$aYN{tdHo~L zt=pVpr!)ZN@?%%oAKGU>4@Ryiy(&J%4y*pd;x}``y%PveI9!DCTMT@*eAZfnmOD|# zEoqzD9?{zq3y1Q?@DmPyK*rWUBQ9>Ug^$(7v%=PakT4s+xv~U_8jf`Io{`211k;qt z$IaQ-g^B11j#PJB0u-+wL#3zZ5^~7AC&6@$qqz!a;GfAB2nK|bt2@S6H@MS%j!%u} zfU=yv_aA3=Cl*HCB7d2zk0r+JV*aLVD~@dc+SFT6SrKvz)OhCq@Q78uEC6_5HfhH#ZLHuuY&Lj6|1=6IsLI8?{D_Zi>!axxb zJ>tYt{|4;bzhVz|AZj)X&?o~5(Bk;FRAtF%TxOk!QE=F z+q~oGguSe3?8n! zI20x7Y^mxh3%vYDE)*uEDc#_7g{yL|p~{`ehxVAT(U$H+Xn zXwF$O;U>A|&_}6tn#J%`U@eQ*iG`vjXvYf2z*WHCv!lxr`sOv=K2|9xz6W2xMS_4D zxX_Nyc52^j+Jg|@AUKCq&4etT<5h<#Y@-wZZOw42x+_8RDM#^oMKZQS0OpDWfTs+` zhRE-C7RHzX&^=N-XE&e$c%f$}e6%<0qk`k?8pv2X3t{rYA3fxf&zj0CnK$ri3~;m`tCvIosP|b*Id_qaI_6qS zcMnBDtG@0=x8q868#vv8_qpxbZ5ZX9dhyRE$8aubxc7CEN((cIf7dLEUgVjWilJS~ zPU#}`b05NRR7)-NoEH!qWFRE5o&8%LkUxzIqdW^-j5LM&tqX>mK1_4u?uaw$LYS0q zGgL~AM|-&BIP53{n8eXx`%a#vNLYMBPZcD#BOs<1_9xZ4l|bx0;_e_mm>u7rI`_8( zW4RoWUysvtXmkb2OREj0KkWm`Z{NlsxLmtXL{cv7&@~w%*hsr{tAxVKg7AaHu$(J$ zYIYS?+&Phv)ZE}(C-TIw5Vse@pUGG1B1pWm zhfHPjns4S*BphMm>OkggR|$irLH8*!L6U?_*hFSUKp9F?JFJLe+iwRsTZtXzz1{<_ z-VMf|qBqd+G8{$>_po73!?Y2vNm9}p{z=@Gj6mB*u_Cng(JzKG=I+43a;6{JrK?Xu z5eE5#gp9!d&k#VTTg}G*Mun?buAxGwMZ{$Iwf}@(+au#GM>68$dm%2Ax5g5>syuPv zfpVB$vRhukpUqRjYpSwl?+>l{q`B1o9tY(vNWxSxxHF9OCbK=*p{0I;8nRSQ=2W9f zGEXx9$X`LkVf8#|gFMX$Sv9@5tDo*xLGr7%SVQZcJ7$2)YXCY2cKu`ag6M~uO=tP< z5tHQnY(;lg3FIY+wRlSVOom8`cmy|ew(dpJd$Re9SY5swIb)NVCGUjbXJe&;Ym6xy|LPozJc^t41jC=*4$v+W9Kq0;<$>VH<^e>B zX1t*}6x8FwQDjoGITX>;AUj&9lnMdR|JNZg|Npvfw65Yi^xrVr^Orkk`!|V0&Lbj$ z?5J{A-BvMiSbno&$(G?@CEY@sCVKV?j_{=IIBsVLD&|oD)}*lbx!l$#O6(3`^YtqA zZBX2p1JA}9V6yxU!c4pdi==g~elPO(;=Bkxi9Uiqr}pfLKv0XlZRYdLyiO;=gqs(U zDL1fL**{k#f0Xi5?{B%P^ID(irq)d@KBcc^+j zHNT8ZX*9Y4dp?MYKifNTbX94$UWKjDxY9v zse8Hdv#(DVp-c{`7^MWVHx!heU`$peb-39S$$}JV86$YkuWoS(36pDl{Y_C{2H`nx zci;T(@Yz`zcT`ZZ?jdSE`Rpz`vaz0H)}<%JVjDXE3jW(W-$4nV=K4exeEL5QVYgy) zdbAjJpk7$8?tUp4|C%I&Xc+`(BmHhNIYqrib80$jK8z>nc?Tcm)y% z&WWBQ|1)R0y*yJD?#yO95;%;_5HG9GJ~4|?aEV#KKva}cDyX#I;hJ+q#TKA86l3cy zh3Kv8)D#B%g8y$!GKAvT0rD1v!P0a0V>xYiE*Q6r7Sw?u_8K_rX8&cn8)*Kol9C+2 zWbR<D%p^fi z^;Oa$s)y&N*n%X!b|Wf@w04xjBxq*wg|gaOgeP43Mi*o?NM7HQkCutT2-W7s&&NxM^de%TT3MG!K%_tY;1WcO&L9YB0dV%MEQp=sH)NT>SC(;IE9RGN3> zR#{m?9y%Q5Q5nuAyL)bs%z)Y!QN=|%EL#E{kj8!OS~P)-U@&pzGQ%--@Huy#nAp?X z>p3uzd&q-3@DTSw8DcX(8ce zZnPa@S1#_XrcO+I#Y|XvprgRu_Q9ngN#=3$HWiH>(#V0p=7wXG=aznJSrNVINbX7} z)fOcCWg5G9lTUG5L!RFiJ9!>PHxJAg$KcwX-&7+ai>L^ZQ z#yeTBqnNcc?ZVUjo!8^Xxk9&m;RolA(Oe9+cX>wTWz>fAg*d>qMbKd(`ZKLahNemH z7cISYYTre1UixAIb=%{WeTSJoMp2=Qc5XRe}UV4oco zPoKk>kN?z|T;m2G`=N@M(by^dj4P_Rj-9}~X_?xI*g$hLLN(Q>JgrdI0fPQssWoJU zqK(Bwr$7b9cTB*Ug%dlAqC$ub-BPN!;c`Zjh36wnL|}QI2FFSKDqIs; zmh4@`xXn>&YCQU3`dBxk4LN+1ee=X6)u{cb8A1iOgCu}Q;Q~kh7qPLXEQ$;@{0cJx zHCcYVc%4=VLWNaBZ7#v$X znqD7k%@mTPT^WgZ9b!@E;&RK{Ikkg#&iQ!9stSoQO7} zZt#cMWeXsfUw=5O%5w40npW{Bz|=3a#Ja#&q|u}n+Hk4EB2ILSRDaJZVheg8&Hrqe z7)@K~Cf_Csx4oGm6ky0f(9U-m)l=3Auk#l5NjIJ))7}(_98R;{!hLY25+@j16s3Qb zKJ;^UZKceN&3{|e{)*7kAmjJEH6U6Vvc6U&q7LBNA>*jDSgCBVaHh6AlYm4|#H`zG zlU&&9V1`0-Qo(ofqgZv;GUj1IW~(c>SCr*g z$roEyp{ae->9SHUC;t1;Oq?nCPXs|ykT_G?R206Y{r-_`j3y^$gaMN_O|derTbY5R zL+i^*`{u}rE4+X9q25z{Hk=gz%C5|>Z4{uxfX9AlSN2$YH-ZZ1UEPhk=^1ZeKkn() zM_Qnj&?(Bqe#q`tLdrd%84-Zhf$P>1paj%M=+czM$N%9f>o|UL1b^9TV4_Aj;oSn( zOKGKx&}ec;@m{*{9)5rAc<$Om>OG!E%MV<~-LUthVoy5amEALU=8V{;^tR4gL;=KR z{ejEkd?Q0Isb2pfyjOt8TS?#}bIp1v@76)}g7?}Ku#xmW+E_8Lne?tX>MMUxmRvjR zUO1oBAhVn_bgxOIW+#1SuTbja+GNIZ2&!q%z9Xs(gvR^>;vXER6;-(E7_p)Le09Lh zx^vS!;;O)Gq=RRyLlA?VvzKY<^$fuFzSqZZ;lR8H}Thd5J_59D%RFUd8DIEm%0jj}RbBw7XZ-M16{i!ZFL>r z=k;Z9hS9#n5T5BiT^nK^MF=n{&sU)zqxs*mQ&D8!3iSnup^o+FDO3I2Vcrd)EcobV z!=(|LEekw5{N=Gt7}WeE@Jz*A`J2K!W#U)ha|1})ScNxjUkGcbn8#C&YdBLi<~nP? zC>`<78>I;_Y`8{#W4UZYtsp=z5uHyil2Y4#%XUNm_6C16mia#a%5cWu$g^$uL7Pb| zn%u4-Y>@7;<+d&SSOXfb|Flo6CyD8o$pF+Oev`3PYFdwH5#;;yrLaQE1`u~%&Sw?ftmQ1wlN^Dw#Mq=CJ>;cYMW&`=!K2` znQmWsfsFMwF_a3tB4)x;+&2<9{%i;*%UcEY6n$PSrNTU*@?P@5m$-C8tM!DRp%&z4 ze$$LDsHsm3TI&DP^l4~GTv1S*DMFUrKb+oGo&VF3ay=uix`AI%ab3;l;AEtqf5E(h zmM{rBzEy&}3IuCCl@`E`mll-ow8EdapYHDzod=5LwRE(HoiVXY82kL>1YhM2&t4?D z^G9=+F}L#`h|341t7gpZKQ}joH@KG=Oq?F86uQWLYOBk6{ehNT5dSSfd` zvkJkmCV~&t;lOP=e~yg+!@Yip?HA^xp!46c&0uk`jSU&?lg`dk`zTZs{dXBPao@`9G(Hjgqe3>g*UY>zrHz z3dHbsv)_92obC7a;c>Swz8>5m zXgH>PC#g#7)g*A|Io8r33g(LXVQwoLlvA~U#j->y2d4sP5$}wAH{Rc#x-9C_l&5N! z_z~F*36=>!Jz$ViNOelD!x`o485G$Zg)tJN1#c(X@5|imt4y_|Ymx2Dp69US7bvS1 zgZTTyw%b?PWPlVSxjSq*GKo7xE#e^cCMjIvDag`mUN|nC_whx6a;iXJVHHVOak75R zL5@+@*YgQxlBTk^(F`Rmrqc~Y(@vmD2n7M5_%{gvE;Kl`O9PU->L?q_*w9I}-c9$) zl%Yg3K-~*-?P7!MsZ>UgvAO)H+W?p6c^hO^1$#n+yG!`nV)Ub$L^olxBqKO6V>Qw6 zK3$qJ5r09x2xdVJYfgA0NS)0OtlZFtw{1A|twwC@LSn~Gv@KmE{!DNFI}oouT7#(X zLrxz69PB;p(g5%)A7VnD2&F7Ad8;D%<3jzI1@fbH@(S~YRx$?uM&)M`y1vsidY7i{ zuuOrdPJzLfiYDX!_#j`C3cXRd~Z^;A6YBI5QJ(k6fo2Ac1TduWeE0 zgyxM|th=L`j@Afi{rtEMUDM#ybbRap->BCU()IK`n6uu@2({4@7RGIb7*m0vHqA`) zv1xO88PgM`xb<~hlVbq>+#-9G3@^R3BXv-u+!oQDL#aS<)rSkj)FxeIWA3f4vX0?` z6ovtBm?q67R8N~k?V^wPArzE54tF#QgX^1b$(H0nN1`AO0!F?kg!KbP z^?d1Y|2F~sXHuK+6PE$}X~gp(p0$!wY*y0cNVCvpUE34w2@`v6?U|HKTS=jCVg zgA;Qs*7WxST?1xfpS;gKe-|7-=VBt&0b z_!dD)fbOB=ZmC4%-TI8e5Im7`#ji32hDINE-G0$@C*D|ZSamaW`BjT(DV^eXT~qHS znQQYb0?WZaac58R<&!xI1>T+api4*O0m>c$1xc^D7{7dZ5t+wveNH2pu0*S;E@;;hIqz+}ju9#}6htXUV1AYa#8>=GxH;f0KgppPcqpAZ zs&c;#nQ4mdM!20z^y5J}I@pLn49cEvo7y)OGv_w_ko@>&pF_J~q%NCB2R~~x`jh?0 z{fM_Wgt!?d9gM7Z?nLpr3|eO0;hc$_CY z{Z2i-tUf4S@mcu$zKo#DEX*d(^wrGnN*eQ^vCnKojOpdLgMv!7gTi^BI}&L}Oz>`3 zom}9hS5=nv(~qV#oDF#CfikHy#x|H+jXdh(1J1$WIT#{g>iLflfXD;i?5`6C#x|Sb zhAW%eaxP7W4x^#(^X()jvhybu_r??O{?G2ewFXA?1`NzdbCx{>L2F0>63AR=-Gl}mc#ni@B}kW z+S)RtD!H4yc|*|i1%O~U%BmOjAeLKvj4SJ!mH*N23gPKk{X`&E*)<7Opzx8epT}g` zP?ym`7Y^dA5g%l9zD^15YznnYRr-Bs_J#X##(6`fH4;aXFANle^*<4H1W;PYbKO)>IU5k$(Hu&&!dmis8z+4c0-kzcdIzD4%O z&og@Iygu+;7D9ihrVN)$!f$I26j&G$i6%dRjYJ(=-xh8@)XIGct-s6*9>1TqnL5!G zIV9HUfRDxcqKe13#6&!%NJ-hcV*Br1VWVi=njnVT2#T=w<&z<;S zj@9rQ9n1{05j2qhc_I*2?2dmWH@ZLh6mCIjBSeM+2T_rShB*^TeE@O~EC1U`+Y&Q8 z2?O`P?pdJGj0M=FC$Pwi9C$K=P4K@qD-aO=zZ%>B{o1|$f&_t)lPCoLHi`Tt6KSFU z)@1&N_&bvK`wOB08v7vvJ7-Y{{?X~5|83d|fd4BU`43Pe@ZTks0`dQSy#rv_Qoalz;XD@o%+Cc>xQ|I`!ZAqyjWwr$(#*y-3w$4;Kueq!6UZ9D1MMh6{slCST%cii_ow|?zWHEY%S zF~{1q*PN@P9K5*-Jb@d6C-FoskzX4c_ul!=85AS|uc4Xn2OOFAgl-Zf2uKDZaISy^ z`2GVK;IgEN@|lb?)gXz6FuYGh5@3qHg$0&e{0*i}L?)nERy<#_K|@+SQD5g|@xA8* zzWcG}xCFUbZLoOS^=(+(@{RgUe8XQ_)9h!Xd?_$;Hg_d=`-exL;5{+m>kU63?arQn zpM}ztFOEyW&4_>JtRKN5^s&@)n$i*c0d{K`zzOFq%K+C{42)JAhJ2>9mT0EH5QH>Vsxhb##`hlRD4oFWpmhLKo+7RLWtgEjE^H z$e~eYVvF{)+DBO7fVjeNQc9r59X&+tC8lz1VlK;`a}G^Ow1HBO$GPmBL6wE)Mvm(Q zU{jf&^wRSfkdX=7R@0f6YfaS(ov60ST9%8rwCHUV$yt}-hUv7@OIfVGVUXk5g4t+{ z?kKFmLSRhee%vZ=X)ewTb=qF0+8%d6TR`1j&HCL_Jhwe#A6Xo*Z$O8CN*5np3nfZmq8ApX|7XQBeOG6Va$7KCpV@cB%AHmy>J$A zRZ6f`lzv~bWm5y=$#ABA5XPstDP8fstiW$+aULTeTjlzkzIMEraGE()-ZTf{|gy)$OO~o|P@4QOZI#Js%R^_rkMj6M!mr9b61&A_to=e=Lo*CW{sws|oxtE8xTgqd zDJwd-n}gh#cQ_&=g@N}M5it)_fU=oy`5w9NG}5Ym{H1v-|4QK|+>>!%kn*pJAaJoa zguLi$>_^t`wqmOM_n?w0ECyoMTW1f zsRZDx1m&CZ+0nxgYRFU06b~3JSz8M_sED&i2MtsYq=#IeFMKt|@oH*Nl3~FSkRrJx z)D)!}qX6@Z;ipzptGn>rA0E1bci(7|g3OJI+HH$^6;Wn(pC9tF9go3xCTm?3;h5gs zb^J#u(YzAwNp1Y4`N{N|0p_`4lLF$KTWZb1q7)D8tAe7CLsvzB&$q~(jl@G2WE0<;;xCNO`Dmh(#w_iy$YX&LR^4uiXavTv z#J$^$4}px6EG@_Mmk2m%7KrvmB?8KO(`3wAjTtB5IxX6q{4^U)0Pju63?JxqgKdIK zG)?mRR=x)HWjBZhHTiN+7%&%Ts}Bjo3&c$EygJ47SOFKvb~5;_KI2>6xR!yZS#bW` zkn23uJzECj5`$;Fl=w4Z*e1VKBwt)07vAXR7nm&1_VUD=g%VQNOC~RGu*%#-p5fpI zyKCdQ!C|%GvkQ$5pn=JBh+*x>rMPKo{e|%0=S{D&ws3$s~2BW)$`&VSIx^C`+0D5Uz>o}+$5kY&t2dHi&N=is#*ga#dV^*he4)LTAgi!Oy;n6j|c-R-USHfSamkef8C zwdG2+{bWqDyi{HKhVrST2U83x2RY1dVxG^~42fFrNvpx6N~0~g@V#Idj|LV9b%*H- zvI}nM`Yprh&uL8jfjG~NEF6Y>)*#DaUXzdev#+H`Qx|XFr|&xwNth(+Oa#Xw8s&LPf$>e;_Vyr%e<#?4B zWztMlVsUQ+scjXlh8mVxQ9h1SBas&8O7|Gc%B9_BT_~j?V?*>aBdcf9Tjk*VAmoD{ zK7G`ky5R(HIB&7Z*066_xBlYkl@~78>t1RsfwDd>kst4NBXyaam^`-mN^g%nI`*#wP=rg%`~94uXq}OL zZ|r0F80KJvK;2o^2wcCZaXKJfU3wf_7APQ>2-ZIvxF=o}AdN=FN@=7!h(N^d%4;5M zJiU-;xzSnr!*(oL#F8>4M818RFso?8t=p&-%vYLH5)jL2zJ}nYcAnbBIhV#U^s8c{ zGBy1QAAPg2fFQOa$>i6OpV;w@zNAKUKUX0iut}%n8%&H;g<)6MhyQ|n@oT2vDI<5=%Zu-{imXgJi3boL9Wb(BMut0t**PQi5dh^GK^2s@_Xei?LVuzE z68QG|nfL2fR2wO=lyuyEp}h&(DP!|`po;(wD08u^#dS^4esCIa#jE=hzPZU9Vx4{} zC*N-&kRMiGn!9751An-K=8Oic9kwmL=n>06LCxwtbpR^9FcT8!u94lW9=3!lwVWQ3 zcjPYyzNC?Ig^|Sl$mRlVRYi@7L9B}2bG*-6yS%46E}MCMy&oXoMx&rGm+%_Xg# zSgS~5y%4Fe)RQ$B*p4pznn2^f$@Sl~Ze;@!iX|C^wIn z767-jQ|Yn(fsXQ()%R@W@~go3pys9S+_-?5EV`4b1@riX7V1 z8E<4FOs7QAqpB(~e>K7pI3~ZNyQhiPA>j#1itVfAOgSzcM2fDj}fpg6D(o~BU&Yb0*{)27HOZ% zwce(vy|iMs_S3EsP#|3JH8|TGoLcduXZcq0RU}-vDXf49#klMklvbX5f;QZ@>)YSnu3^RumbxkGBdQ)%x|RAXYC27tYO&kd4pwa% zRkb>}V$2s|4$Q38%HBiYrFl-ax%4e;vP35{yYPl^jn{3K2<11xayX6S9rWoA^ERV z!kqK{S!iP?6o@=R+JekHK~_}jfpXv_vH{ZV#yIJ7b)+Lm2ZACX@5<46H7W6*pZKtm zXEp<@EN}~Zu1pi)Er#gSG9w5GZu%AAJ210{B7%&&r<}q`y~uD!PP*V)?;yw0;G))z zBRplx_lSB;(w}2o*F3G#<71g7Ja5V=A+cGgwEd<~g!%d#HLRC8h7A2wS2OcI#g@#A zGq5J;_Q@P(C8Ey^vGWIJ^6S?Af<{h6Q_tP$hQn;(2 zdr+LknS?bNuUQI;uq>4Q2hI(>$}Srs4});HeCWh_4C!)4Ay(VK< z>VMl@q`t{u%!xzMnPJXYw67~I%_M92Y$`${oInQO8RA?yCgcW+Uwp1BrnG(Y-mnF@ z)@4F{w-m$7R&6RxVt6p{+~GH@&}>2S_(^~8@c?DMez>;xf&9`pte8DaPxhD(x5OU< z8C=-CG>)8C{`C0l9dE&HeN$!_IRXe}#!|i-_+p>&^UT{=GKn=1C)N`SFc^b2!!P9= zG)?v$!c}RZ#2=DwUw?e#hMJe|X+;K9;3(G%KH87?2j1eoq0bHYy?}$mFGf64FV?;; zE5lriGmTo~MW7mbb;b{voHal4&tiN`46;K!vphNz7Zk215+oXu?s{z$`VeKGSwvnS z#x!cgG;mU^^Rd|l??m&4f10OhNB4%oDxlf3W)>zQgISAIX~FsiFWVbUMA- z1!`oLU8EV^^ELY%hp=*3Sg{x3hFIk<*ezWr7UW}?4oy#e^qqY5N5Yru65K7%d3|#k zR4&FS@AM5tvJFRQFHi{rJee#>iH6|YCEHnk=gA8BDt>S${UQE$2{=mQDB%MG0a=9r z0g?KK4-~dzlP~Qwl7LTv+uU{ zFt)mWDlV-JF0C7qKKx*g#h4$A&or7g!weGBp!LJ2^W-OR`N z>fW*PvGsF#o+CN#x2;T#d8~aQoA=H#aRj$&&)|V-2YHIwU_T?ebiD$>kjeONFu;V&CYa8Hej|h3Da_;qp~%#d6&c&_HEF+X&Aw^vUO)8Ky*x zX}(~Y8d7>9!3}i4(%AqhuFNa&Ba;@7n{9@(vEHKE%?7Uz1Y541hdDe#EdZLqx4dzp zU2n(BT+b%wWizJ3!4;S%tTwW6fR|cGRj+tiOjmZ>V-(3jZc!>15h-j9`BB{0m7gf- zKk%YGf;+EhZg3Z0JoZU>Kc$VjJp4UhUTmiyLFzDS&S+LK&4NaMs(MBUdgu20hX6u? z$Aw07$}MB1^b6?tJw>oyrD3No z-k(x%ZBsbt8{G7v@YNXg_7#lrF}EA?W9tq4aX-;!=8L~;Ja`*+CpfzGJ@EDry8F+! z!T6UK9%5mVLmZ;yU#7Tj^@A_40BSEUnwY%%XJg#>Jxc|^`vcq`uxI%f$|^7Q!8(d; zlyB9=w%R8Fw)iIzA;6HIX`u!7b=DW|%3>}A+o)F8O;?GlsCv=VkE_~)R@V)qqa5rz zmg#T)Gw!ZkR9S#(HvtI))GTQ%qdWECCY;EUX(vvtG{>lRKXfP@(s|Xb zRR(V^H+^|P@k?roUwDgC4d*dlyWnR~w&5K#Z4aQeVtTIz@4}YhR=^;j`bh65Ti12sa=Jq&Sl7kF&`SN5 zI;<_OSE8>ISk?M-=JnI1w7x3iyk?&rVU?7tNBqgOo}uopW0eQ)fdZB5lUznxBN(Mr zZ2v}e_m!KQo(tn_HQW^8%C(;;E}blM^SQlY^g6H6R!)G z*Y!9bppH4*G_yp}vin97MwdRo3)=)YYpOKh;wd%w0B6`A4d*{LaJG>p*ABH}Bh?8g zQaWqxHG5^=e@6Sv-;2Kr7Ty<&yZyLV`Nh`tG3O~cAlXenv;C-5hm zz&Og*ID*m@NXMC}3sEq|DQl9)Qfy{`Uts4!51SRo@`g2InH|nj`O**OUs8ludfIM} zc9@>tm2f>j7z7Hk$ZyKas?rS8ws6Ddbp?6@_-n!pb6^Cewj)D06TRZk8d4D~xv^i( z(TDoV`Ufa^$iMCkE5YfTwG}+FGoNO;Y<>#LucqLFzIQk9NU+EMTK|cyHd6kLXYh0Uj)cP@;^^-hci5ACk zxi>WFc+DI~YG?1h0%mvgGJ;94^oW~A*&FJ5vZWU*xM5QKliU}Tcbp|Wi%Dn()k2?3 z1_s|3?e_hwZ}MMny@TZIyBd?iG2@#;EV21sDXxhmXpI9(tqw8d3QeE@B@4VEVV2*x zLhr4XFp8}nFa{Sc<}oxSs$l8!?kIL7E>jY$h6O-%1fuBfaJZs??Q+>-HJ${q2e6N* ziqw}~7gL@3IlXv)CfWeqynqG_I3 zJqOhu!EU`z(}-_()(8pqV9<^(Mu#Xm%7 z-*mmbMwJiWMqG0FMzAr;(FG`>ExSEwqlL2p`+b!5#hw8KvPxLy zSL3qF<~nCSHJ$lun!i2?xZZTRf4|xh7cRKr5kCdYg(?;Q@HgiJ)547FXV^yf$+EaZ z)zp*hUkS3Pvwg$QDpPT}_yD|`*f8e@=O?+W3xxB@jlIbXaTGGx7^; z%9!bfw3T=30>7;J!^Xt!zd-)I?W>Bx5|;n{yL^WFd%*nX@6wXl(%IO|&YT&z>y8D? zjV1&bC@&ddjD5{<(4tJ;LDGuQeE>Z&%NfXH3L3B-w^XdFfmz=G=^0ofE<)7Rdi_S zsg{zoGZL)$Bb-np8*Yg560Bn^kL$oZ$4$V(HDX#9U+|L%%(fwW$Ke^$kp9ocs*%bW z`clI!*4TqgjVdPt@JWoSmx0bdmviTnmWc#+V{j3lBJ?=2{QE18@97ucEl)kHWSmGfQ=AAyfIyAS9Nql}Rjt$zJ{iZHHB!(yL)+7Ss;cy?z|ATs6g zFER~tX&0~L{$-$+W^B+SIvGZIyS;>RVibpBql4N`ZIue41R`4a?+P5ncFGN7 zOklGR1^j@59i>cnj%gJa=03*xA#xZ?u+)x`k0l@|q7pav*`}Owa|Lg5RTaMED^8pS zrf5tSuthpmzgrJp|Iun@$StI?zEwYEcv#{J2{e?{nPZpc1jm)I;-R!ceMP4?hPcPp z9F$ySf0UqmLjE_oTl?!>(t(a~a)4_k^uOn%HiOP<_Xf-Y|2=3E)J_aI&hauhG*$}4 z%w>)NK-;x0c3aoU&1$^#9qgY&WJQTs3ZVdG^K89T-Nj7FgS^?<=?s@rkCU6vKfgbq z1H7uK6NV8H`5N!7#ftK>@TgczNTJ0?nxQb+=`1)YSjtQcB@;E@dh-?}N^GRUKsrDol8d`?(ac z!*1^Srx7Gx)j!`q&Ib8~c1ktf^$IN}qHJj2Vk@FTEO9|{N%y}g9&~3OyRQZb z47MWC^(8xsnLnpe1kZokJ(uU?+6oYq#Vp{fmU&o03^|Vky6Q3sN)~>ziVw4DJRPS; zC6nW_SGWzvy`m?R@&^zvD2pJ)>h*4K;%5>$zyqOga5Dwc)`N6Nia+n>m}7Q7)we;JC5<-g_u$;0SzaoTA zy-eOdY1qdNykw=p9AoWiw14T9jX18nLtwpl8VuyhBt_KUL9cIiM9@beg`L4m7_Pm>MFikS-kHI{`2WQnjj%f5+&0AfFZlJj>AgTxV zdo-gnKFH$>EmFdW`y3}<;-r0+LV0d`o!>`SX6Z|Dsg$EW&}SW8;(bN{)`v)PVzzExKz zr43~CBNl!!B3_|KK1^Udfs@Vw2cLCELbhNy;8!dRDEZ^np_qH9N?{600P7a?cX1mP zvp*@>l;`?6{Ne{-2h{FyM;?y`VjDTPX!a48WPo-wkGIR(**sFW7&G+opx?zY9GeKK>i1|~+ipO)X0esq}d zDIuip_xT=~B3~Nh-Jv+Q-H{B$MfFepSSX?FjrF`~ZiwgVyP#y{xmjMN= zOGN@KA-5*C7)++oWCm$$Fq+dIP)Cg7p_7B6BS3v2UTde?d1P&moE5!5e5Oc{EDS^c zDv0qYr5D?TcyT%1;di+aJe{4s6&CgfEkF24Qk$PlWm=jjsuSiytG<{XUSdp&h-hwI zxyVrJsFiE5==gmIJdEc#3!$S$@H0^l$s-G}YH_yd;F4k7ZyVlvn$%>{<=#HYqIV^@ zmPKpNN?~It&SJJ^r&WF1<{t8uddnl`et)s`^PrdUkR}7h<2pce_ZuR=IgRge=U^SL z01U*nu5IpabEeP_p214~R*eT;#v3lPXznfKkncRP)SwxqFyGfjH}~__yrSP?u;f1g zA*Etyc3*aNHxcisjNq6MAJBENL+k}lri6n}WX z`x|sOAJ32Z&`rxn#2Mr8IpZXrB*rlym>5=Ac~+a^(j%a6m*m}UDv7`FAu6!e zN5x&HFaAm{aHhRL82#(v;rA&dVw=te2(x=nN%A@rWf7huiQR+`@bhn(<6OFQlsrF9 zdUY{wK`mT+msXt29fk>fv?F}1;wUDM${H#uWvmuY#?EKqmsn*L^3>=&;5aq; zMuhNsz_{nFLz2RcA&Lo`e8$o?ZJZ|gI#5eeVS8JRP`y&8QKSKr3X@0Jo ze_v$O5}{pCGOj8DLra=OMWu+xI*@Q?PXlB#>ymeG8wNls?Fg(#W6N-`=6)2#{Ak5Y zDdZ$_--&Edo@CB*!eyxgYC@-wF5@YxyUs?9|%BJJ;d&p%>@2vn!^J zR>`BIW>0O$4%FA<9P!#NBr`e)1td%df=Wy==j@)rFc>w!ZemA_PbOL(JgSk%KO;35WL|kX?*HV z{hl_L+}q`<+(Aj0EP*C2TWvBkj2>3z$EztpQsqFhEfRX6{)9B}Ap~RKL=_2JjH|ll z#cucKbox(f^%6taT~_A^fMxTG%8H5LoNKV?tYqGC<}rGJO^`GC}QZKoV*ivI=^AL|ZJQ><-c%>vXnX_~aVAOD^SaROT6&euq_hha_d^bEjpy zJ)S#|$uf(2dVSTQIfuf%k!RFxk`{ptD>&avC!h#``=x^Lz-smi;5m7Lxc5b)6bjYI zjfAOji*kpxwntM#ogmyIjYaR!Fmp^hGfx~6T#Q*pLY;Gu{4uW-b2Kk@2R|+O#>BGz zIdvY+H=??cdrtL}Vxp_Yl)H^-8&sb)_7K#%#wfYjI!Oj` z(H`@g#0mdDjKun1S(_2lDhT>-HSPK<7qIMypGSd_6 zVJ(n-x6$zDP={_f`*p&GQri->R6y_^dAN@$>S~E5Fbc>wN}7@!D?%dtPEQ(o-F1kl}TLGyv#a zPkM^*K3-QvRt1vldVo^Fo15K$xVO_A60&Y>|ND@dk(q6pRJ&jwoMyEZI(wEPgsw2XYG5bgq;u2LwtHDyHAT|;N*Bit}>W*Ef^KMC@U0QgM!FLwz<`r z1?_LblQlh-EY@i$w50yw4ZNE`97jTAlf7^n@$YfcYgd^KGxE3g7W`_{G=U6O<1?}c z@~NZ6H5o_u7}OE=orxE7ZW~ z+z{BWz0tM7SbXlzCY-z)Fu2f?W^79|5Rg&2zcx@N0KBLAN{b==0}l`gPLT)_EI@`l zVVVRY7VwKrLOmIR1tu)qQ${H%a6%zH14N~~aGCD6N>!nepH@YUrlf3yN_EYxiG7vd zwvgTJ_3s_MO`jh=o8Gp1=Y3BZ8Pf_#R8NP8SAsruJ*yj+cegDbJAYm}&?Pvcl}{Ft z-T<$E0Fpw)6Q^}}AB<_AO2n5(1!{cPMvE1ZI)?-~uGR*8xL2z`hYT}TIi9Suv&Q&N z&(-_GlwJkI4iot>@5%*cPlE)Qj@Xabbq~pMkbdD1$FEom#4oMaEm08N9MMtnQVunL^k<3x!Q1H9`)+3HR7wg<-R5nD81agYlS=YMiBXjhs6piY=UY zs3Ci|#5g5(n^AI=*fv(hT6WXbD935IF+kAWo)Udl1nsV2POk(1QoH=n=1`)6v5#{L zZmiD2Xgh8j2a@Q)NLj|E5uGCr58O>=hbb~ZHoDx2s`M$)QEUW{tFD|>SmyPuOx`e3<` z7Bm$d zXe(25v12my#IYD|3@{L&59QPKDN8QXx7-r3C*b&%`EZV6=0pv4990B9DL@%%d zNOmAOQy$!U69L0SCuzkhH33wOL;I2J$%C)})*0MIyd@}e@)#W(7n`2Xv>kj2>3#Ha>epEqHVyvgvjyZw%_QbT;vbF{O#qZ>VTEni3gY^@ zbyz3KvT5j{%4^S-Lo{2WS0-MnMq+I{M1E1&xJb4*;RVmQnUlXW4hK1Va+q^2N_F7s zit&v_W~RO9*sq+_mV%`OM%Rt7CDy}s@0C$x5#=?%C$v(i){aiip;%FcZ|yfyRdL+!i-kT;&-81dQ2)1&BuTgnZ3yXr$3#VbO~#6{bZxDRp4%hx?$4`z;`b_bym zdQ>y*H&pnzKA?k-&>|zo{^W)XnAr1q6#@j`Vv7s(d*J*b5jOiZXPKMq`>ED~Pzt^HN6%?Fdf!Gv)->wwt)<=~$ zhUg#Em%C*Nwmh_1ldA4Gi%<1%C7(T@e6NP9R}m{U0stmB+GM!iMSlc_)nNll@81O@Lx;bw+-;e2UV%^v~eSr#ofAxaNsO^3P1EbZ{ zXMjf#BDLlj#f|0kS2^noB%`d60^exOAjRl1Ampi53+1t4_PHEkFCDYMfHobmfPtA` zG}sCvBe82ykHTV(%;>ZJ)kcADb2076gT~CIrwfQ8x3*QRMx>U`w9nMuiW!aefIPe& z`P(~G$8&|>uDqLq>Km-H`IBij{96G(e4>E|4ap6gTYAum9Ve_X=h^~iGZHE*I%{*k z!6HN(;f}UZy~$kam>R13+Bfz)_Y0zmnb<^Wi4T{Xp$0zKZV$d%PZVR;lpLZz5Vcz^ zECxWSk2Fh|WHo*o3@P5ovn@a9c(DDN6KMTZ)+db!9i{vr#p>bd$agkj(*BzL;W&Wt z!jV?8zvp*SB>j4RPN>TK(S05_QWGbN4EyBN?U-LZ$5*bQBkVoT2ng;xu`Ixv6s*HV zze<9$I^655cXiAVCIj#67s33q$5C7S=n5z)`r-(!I^$S<9gWYp9-;{e>y<oF6WDebIVaI$;_npTRqkYR^6t8;Q>34o`=K`UouvA(yALi~Wp~bUiN00R3 z#HN`_q_|F(=nl2JYes=Kmgu4Zi8kxF%~!V~NA|+Z^}*Jb_S&j8Sz)xCCQs@CIR)Ts zskMH<82)Sedh;&F!b~h3N2h0PnqbsLWVI)4<_maztfxXy7WvzA1Tl75Me zdd*k&r&lC9y?et}W>0D7BbWh!S6-~GOT zy>ukR`y)~(Zqu`AWeF>+Mv6H(&ePEPjc;e@d(h{Frs5Uh!9g(uZNx@W7G(MM^AFY9 zE*$B;*O$rEF|SBmxTPuGn>7yRxryMw-=*4PjE_ewK&aq*Jg?Y#X~XM!|(k;$|bfI-}np3^=qpx*et>| zb?@@-JVG&QCI9O90V;%aqf&cSm=3LMc4o|VZP<*id-{1t+;p*7e`a9P7fnWjRH`W5 zkkKK|218b395vb=lx4ib`=S8zI?chE5##cQC7x2u(hj%7kCH2WgkMgHS|7ocjLAMi zDxW+Z^asy0^9LJow`sp@SvwYntaOCEGUJlk(wYtL1#57&qU+*Pt!QJ5UCxCb5wfyx z%Ag}Ks-V%Uxq#9ON(`4J`t?g$-n6$k9rK)-liNKU2RL^ihhj2Rx?zA@u==VZSl3)M zmQjl&6q259eR!iHQqH^b{$@rylBD~+~Q8ilUZFI334pACRf@1L|pD=HY z*L8beLytHeAr+S-k8|Mo9>T}mztu$5el{Il6p57@@)e~?tv#!$sObdwSSyHo5yy_K zj9q&?FYuI#`>J~Tm;M0Ezu+l{?k< z&i=8YjQYkx7wisaX5lhg@;lvI1zd~;*6c;WPceb6@YzIno7lPA%L3l;^7&hN2kgpu zP~6_kR?7JCcZ=|SBAemy-;7yDOcKH zONQTz{QAItc_zn|^0uVv1>|~P+L~E7Zg##M0*#Msdq3N(mJ<&*^vdFJ9&fkNGI5_m5Q}hon1asoD+!8 z3Ws8L%-d9D{uXs`DAbaxTUyFr$`9^Y4bS-iUG+xmvwh|Dh7v?hdEl=fW|jR-;K7Ex z-~;itzuFJzlXM`IPE|Vou0kvbo%Kx3Dy1TrmsrC!+!OE#>&+U-=fX;cTbI&7T51OZ zW4G6dFGnB;&cq0*51pfPgka*tti5&GZ$g~(L74^FRnQUEpcsVT)12SiU2q`LvZ--R zdymmUEGCELWwGW7KCx9fG2o7UGXM5LG`xjAVhJSDbv{~t zHwS0sg$bf?Wi`mTa$rg4HoUV5K*hecuZJ}o$>7v{}|pEH#^Y0=m&gAAcP@zC~! zh<0;j!H3Sk)vL%v#Prcj9GS4_L{6{TF?D=yL8Q4Y%8N8Jk*Hq#2b8|}Dd8+h|2A?D zp+Z(r#u4zlQ0c3y-{+|b!YHrTUGJ6iih2eZFT$-E^M*`_$BY+i~I2vS}dv$k)6-Y1o3BJ3_^Z!yc!E{I>PI+lCM2ppW+V*aKE1hZQ1 zj1_D~#n(lVxv@lVRfU+})c5S@p;dc0`KEj)L`?(7>pGVRITmwm`G;<++XZ42f z=9KBn=@tJ$F0fl>4bnx;`pzHn^t=p+TABY%Gq^3!X(pZw-G~V3kEg6^?SwGD4%Pl8 z)DzvnxmP|)Q4X{Y+(AAIhk4ca&+wNJmO^`Z)p>*Ea9ptKh2Geaa=5yJe5;USm(Aq0 zvKIuTEq;@a5kUjpfkz-;5L*g@;|U%w4BMPcf%E4Z->vdLp24+vD}4oH7vEta-xaeh>asgzU2Enw67+a z7IUz_22`A9e^Go9e=GouS&_xru6~y-*Ocqdpu`FXyFY%z(7q88b68_n*Tmshy0fHt zY_24QiVCqg8W(u{tfugbsK$yRkThtoi!{M5+>Yhf~;7 z9{kkd)`@lPhLacKF1#^U*hBOnX(b%~1l7#l>2e0LF&F$v)yxeS>_9mGNciJM7^Eqs zi4YkM;;%CW8b+?_mp!n1Q2C!x<|gE!NtpkCeED$73@pP9C>lZzw425z_|KRe2nhdQ z^U8m&Q1^d8zuobGThl0z|2^w-H;oB~@BPmM`WZ5a|C+4;s?1=4A^ZJv58#LYUtog& zYqih3#nDPGr6B40-l5%H$RQP65YS^d&>C# zUij}+{?&p0SM>Spf54&(qy+!fq}=}-(c!Pb!qI?YTt%9WxUh%^cGCxp?-c+hETI5-m+-&_ k2Y?z&=3ps9!0shhh<^xow}b_DG6aNOrh^h5{;!YxKjAK;s{jB1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7ee7399715..bf3de21830 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sun Oct 15 15:39:27 MSK 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip From a735f9ea2a58c386185e3769a534ed7f3bc5ec05 Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Mon, 9 Apr 2018 10:06:11 +0200 Subject: [PATCH 19/61] updated dependencies for android sample apps --- .../animation-app/app/build.gradle | 11 +++++----- .../animation-app/build.gradle | 2 +- .../animation-app/gradle.properties | 2 +- .../example-app/app/build.gradle | 21 +++++++++---------- .../main/java/com/example/app/MainActivity.kt | 1 + .../example-app/build.gradle | 2 +- .../example-app/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle index 380734c25b..a32834eea0 100644 --- a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle +++ b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle @@ -3,11 +3,11 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 26 + compileSdkVersion 27 defaultConfig { applicationId "org.jetbrains.kotlinx.animation" minSdkVersion 15 - targetSdkVersion 26 + targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -21,12 +21,11 @@ android { } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" - implementation 'com.android.support:appcompat-v7:26.1.0' + implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.0.2' - implementation 'com.android.support:design:26.1.0' - implementation "android.arch.lifecycle:extensions:1.0.0" + implementation 'com.android.support:design:27.1.1' + implementation "android.arch.lifecycle:extensions:1.1.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" diff --git a/ui/kotlinx-coroutines-android/animation-app/build.gradle b/ui/kotlinx-coroutines-android/animation-app/build.gradle index cd617a0956..53e41017d1 100644 --- a/ui/kotlinx-coroutines-android/animation-app/build.gradle +++ b/ui/kotlinx-coroutines-android/animation-app/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle.properties b/ui/kotlinx-coroutines-android/animation-app/gradle.properties index dae08615bb..843a98247f 100644 --- a/ui/kotlinx-coroutines-android/animation-app/gradle.properties +++ b/ui/kotlinx-coroutines-android/animation-app/gradle.properties @@ -18,6 +18,6 @@ org.gradle.jvmargs=-Xmx1536m kotlin.coroutines=enable -kotlin_version = 1.2.21 +kotlin_version = 1.2.31 coroutines_version = 0.22.5 diff --git a/ui/kotlinx-coroutines-android/example-app/app/build.gradle b/ui/kotlinx-coroutines-android/example-app/app/build.gradle index ef715c4d93..e6430751b9 100644 --- a/ui/kotlinx-coroutines-android/example-app/app/build.gradle +++ b/ui/kotlinx-coroutines-android/example-app/app/build.gradle @@ -3,12 +3,12 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 25 - buildToolsVersion '26.0.2' + compileSdkVersion 27 + buildToolsVersion '27.0.3' defaultConfig { applicationId "com.example.app" - minSdkVersion 9 - targetSdkVersion 25 + minSdkVersion 14 + targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -22,14 +22,13 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:27.1.1' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.android.support:design:27.1.1' + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + testCompile 'junit:junit:4.12' androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:25.4.0' - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.android.support:design:25.4.0' - testCompile 'junit:junit:4.12' - compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" - compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" } diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt b/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt index 9f04427c15..fc1cdbff41 100644 --- a/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt +++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt @@ -10,6 +10,7 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.content_main.* class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) diff --git a/ui/kotlinx-coroutines-android/example-app/build.gradle b/ui/kotlinx-coroutines-android/example-app/build.gradle index cd617a0956..53e41017d1 100644 --- a/ui/kotlinx-coroutines-android/example-app/build.gradle +++ b/ui/kotlinx-coroutines-android/example-app/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/ui/kotlinx-coroutines-android/example-app/gradle.properties b/ui/kotlinx-coroutines-android/example-app/gradle.properties index dae08615bb..843a98247f 100644 --- a/ui/kotlinx-coroutines-android/example-app/gradle.properties +++ b/ui/kotlinx-coroutines-android/example-app/gradle.properties @@ -18,6 +18,6 @@ org.gradle.jvmargs=-Xmx1536m kotlin.coroutines=enable -kotlin_version = 1.2.21 +kotlin_version = 1.2.31 coroutines_version = 0.22.5 diff --git a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties index 9a0efbe2b5..485ca2ca57 100644 --- a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties +++ b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Nov 05 23:56:59 MSK 2017 +#Sun Apr 08 03:14:27 CEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip From 379f210f1d6f6ee91d6198348bd16306c93153ce Mon Sep 17 00:00:00 2001 From: vkhikhlov Date: Tue, 10 Apr 2018 11:15:59 +0300 Subject: [PATCH 20/61] Remove unavailable builder from js/kotlinx-coroutines-core-js/README.md --- README.md | 2 +- js/kotlinx-coroutines-core-js/README.md | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 1752c0d8d1..631bf8aff1 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ To avoid field overloading by type during obfuscation, add this to your config: All development (both new features and bug fixes) is performed in `develop` branch. This way `master` sources always contain sources of the most recently released version. -Please send PRs with bug fixes to `develop` branch +Please send PRs with bug fixes to `develop` branch. Fixes to documentation in markdown files are an exception to this rule. They are updated directly in `master`. The `develop` branch is pushed to `master` during release. diff --git a/js/kotlinx-coroutines-core-js/README.md b/js/kotlinx-coroutines-core-js/README.md index 19941b69e3..21ed982ad9 100644 --- a/js/kotlinx-coroutines-core-js/README.md +++ b/js/kotlinx-coroutines-core-js/README.md @@ -8,7 +8,6 @@ Coroutine builder functions: | ------------- | ------------- | ---------------- | --------------- | [launch] | [Job] | [CoroutineScope] | Launches coroutine that does not have any result | [async] | [Deferred] | [CoroutineScope] | Returns a single value with the future result -| [runBlocking] | `T` | [CoroutineScope] | Blocks the event loop while the coroutine runs Coroutine dispatchers implementing [CoroutineDispatcher]: @@ -49,7 +48,6 @@ General-purpose coroutine builders, contexts, and helper functions. [CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/index.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 -[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 [Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-unconfined/index.html From 0bb18fcb4a482bfc8f3dd7bf496f041f91f84d28 Mon Sep 17 00:00:00 2001 From: Dmytro Danylyk Date: Tue, 10 Apr 2018 21:10:41 +1000 Subject: [PATCH 21/61] Better way to set CoroutineContext#DEBUG value Make `CoroutineContext#DEBUG_PROPERTY_NAME` public, also move `"on"`, `"off"`, `"auto"` to public constants. Fixes #316 --- .../coroutines/experimental/CoroutineContext.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt index 774ebe78eb..0db53e88b1 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt @@ -21,15 +21,18 @@ import kotlin.coroutines.experimental.AbstractCoroutineContextElement import kotlin.coroutines.experimental.ContinuationInterceptor import kotlin.coroutines.experimental.CoroutineContext -private const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug" +const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug" +const val DEBUG_PROPERTY_VALUE_AUTO = "auto" +const val DEBUG_PROPERTY_VALUE_ON = "on" +const val DEBUG_PROPERTY_VALUE_OFF = "off" private val DEBUG = run { val value = try { System.getProperty(DEBUG_PROPERTY_NAME) } catch (e: SecurityException) { null } when (value) { - "auto", null -> CoroutineId::class.java.desiredAssertionStatus() - "on", "" -> true - "off" -> false + DEBUG_PROPERTY_VALUE_AUTO, null -> CoroutineId::class.java.desiredAssertionStatus() + DEBUG_PROPERTY_VALUE_ON, "" -> true + DEBUG_PROPERTY_VALUE_OFF -> false else -> error("System property '$DEBUG_PROPERTY_NAME' has unrecognized value '$value'") } } From 590c8889c2bb5341f15ee20e440c5599342fe34b Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 11 Apr 2018 22:21:23 +0300 Subject: [PATCH 22/61] Documentation for debug property values --- .../experimental/CoroutineContext.kt | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt index 0db53e88b1..c601a0535e 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt @@ -21,10 +21,25 @@ import kotlin.coroutines.experimental.AbstractCoroutineContextElement import kotlin.coroutines.experimental.ContinuationInterceptor import kotlin.coroutines.experimental.CoroutineContext -const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug" -const val DEBUG_PROPERTY_VALUE_AUTO = "auto" -const val DEBUG_PROPERTY_VALUE_ON = "on" -const val DEBUG_PROPERTY_VALUE_OFF = "off" +/** + * Name of the property that control coroutine debugging. See [newCoroutineContext]. + */ +public const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug" + +/** + * Automatic debug configuration value for [DEBUG_PROPERTY_NAME]. See [newCoroutineContext]. + */ +public const val DEBUG_PROPERTY_VALUE_AUTO = "auto" + +/** + * Debug turned on value for [DEBUG_PROPERTY_NAME]. See [newCoroutineContext]. + */ +public const val DEBUG_PROPERTY_VALUE_ON = "on" + +/** + * Debug turned on value for [DEBUG_PROPERTY_NAME]. See [newCoroutineContext]. + */ +public const val DEBUG_PROPERTY_VALUE_OFF = "off" private val DEBUG = run { val value = try { System.getProperty(DEBUG_PROPERTY_NAME) } @@ -64,10 +79,11 @@ public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool * then the thread name displays * the whole stack of coroutine descriptions that are being executed on this thread. * - * Enable debugging facilities with "`kotlinx.coroutines.debug`" system property, use the following values: - * * "`auto`" (default mode) -- enabled when assertions are enabled with "`-ea`" JVM option. - * * "`on`" or empty string -- enabled. - * * "`off`" -- disabled. + * Enable debugging facilities with "`kotlinx.coroutines.debug`" ([DEBUG_PROPERTY_NAME]) system property + * , use the following values: + * * "`auto`" (default mode, [DEBUG_PROPERTY_VALUE_AUTO]) -- enabled when assertions are enabled with "`-ea`" JVM option. + * * "`on`" ([DEBUG_PROPERTY_VALUE_ON]) or empty string -- enabled. + * * "`off`" ([DEBUG_PROPERTY_VALUE_OFF]) -- disabled. * * Coroutine name can be explicitly assigned using [CoroutineName] context element. * The string "coroutine" is used as a default name. From 4cb5d19f337a42a6dc7aa7d19d14248c6bbf01d6 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 11 Apr 2018 17:42:16 +0300 Subject: [PATCH 23/61] Allow negative timeouts in delay, withTimeout and onTimeout on JVM Fixes #310 --- .../kotlinx/coroutines/experimental/Delay.kt | 2 - .../coroutines/experimental/Scheduled.kt | 2 - .../coroutines/experimental/selects/Select.kt | 4 +- .../experimental/WithTimeoutOrNullTest.kt | 14 +++++ .../experimental/WithTimeoutTest.kt | 14 +++++ .../experimental/selects/SelectTimeoutTest.kt | 61 ++++++++++++++++++- .../coroutines/experimental/DelayTest.kt | 21 +++++-- .../experimental/WithTimeoutTest.kt | 59 ++++++++++++++++++ 8 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt index 25775d1206..bd9ec4adb2 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt @@ -35,7 +35,6 @@ public interface Delay { * immediately resumes with [CancellationException]. */ suspend fun delay(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) { - require(time >= 0) { "Delay time $time cannot be negative" } if (time <= 0) return // don't delay return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, unit, it) } } @@ -99,7 +98,6 @@ public suspend fun delay(time: Int) = * @param unit time unit. */ public suspend fun delay(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) { - require(time >= 0) { "Delay time $time cannot be negative" } if (time <= 0) return // don't delay return suspendCancellableCoroutine sc@ { cont: CancellableContinuation -> cont.context.delay.scheduleResumeAfterDelay(time, unit, cont) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt index 93a522e789..e06323d105 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt @@ -61,7 +61,6 @@ public suspend fun withTimeout(time: Int, block: suspend CoroutineScope.() - * @param unit timeout unit (milliseconds by default) */ public suspend fun 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 -> setupTimeout(TimeoutCoroutine(time, unit, cont), block) @@ -151,7 +150,6 @@ public suspend fun withTimeoutOrNull(time: Int, block: suspend CoroutineScop * @param unit timeout unit (milliseconds by default) */ public suspend fun 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 -> setupTimeout(TimeoutOrNullCoroutine(time, unit, cont), block) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt index 85ba53c250..b7f61ce9f4 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt @@ -52,6 +52,7 @@ public interface SelectBuilder { /** * Clause that selects the given [block] after a specified timeout passes. + * If timeout is negative or zero, [block] is selected immediately. * * @param time timeout time * @param unit timeout unit (milliseconds by default) @@ -416,8 +417,7 @@ internal class SelectBuilderImpl( } override fun onTimeout(time: Long, unit: TimeUnit, block: suspend () -> R) { - require(time >= 0) { "Timeout time $time cannot be negative" } - if (time == 0L) { + if (time <= 0L) { if (trySelect(null)) block.startCoroutineUndispatched(completion) return diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt index 69b9bd4464..5116d129a2 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt @@ -192,4 +192,18 @@ class WithTimeoutOrNullTest : TestBase() { } private class TestException : Exception() + + @Test + fun testNegativeTimeout() = runTest { + expect(1) + var result = withTimeoutOrNull(-1) { + expectUnreached() + } + assertNull(result) + result = withTimeoutOrNull(0) { + expectUnreached() + } + assertNull(result) + finish(2) + } } \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt index 1aa236846e..220a12ba5c 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt @@ -179,5 +179,19 @@ class WithTimeoutTest : TestBase() { } private class TestException : Exception() + + @Test + fun testNegativeTimeout() = runTest { + expect(1) + try { + withTimeout(-1) { + expectUnreached() + "OK" + } + } catch (e: CancellationException) { + assertEquals("Timed out immediately", e.message) + finish(2) + } + } } diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt index 26bb748b7b..10ad327016 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.experimental.* import kotlin.test.* class SelectTimeoutTest : TestBase() { + @Test fun testBasic() = runTest { expect(1) @@ -40,4 +41,62 @@ class SelectTimeoutTest : TestBase() { assertEquals("OK", result) finish(3) } -} \ No newline at end of file + + @Test + fun testZeroTimeout() = runTest { + expect(1) + val result = select { + onTimeout(1000) { + expectUnreached() + "FAIL" + } + onTimeout(0) { + expect(2) + "OK" + } + } + assertEquals("OK", result) + finish(3) + } + + @Test + fun testNegativeTimeout() = runTest { + expect(1) + val result = select { + onTimeout(1000) { + expectUnreached() + "FAIL" + } + onTimeout(-10) { + expect(2) + "OK" + } + } + assertEquals("OK", result) + finish(3) + } + + @Test + fun testUnbiasedNegativeTimeout() = runTest { + val counters = intArrayOf(0, 0, 0) + val iterations =10_000 + for (i in 0..iterations) { + val result = selectUnbiased { + onTimeout(-10) { + 0 + } + onTimeout(0) { + 1 + } + onTimeout(10) { + expectUnreached() + 2 + } + } + ++counters[result] + } + assertEquals(0, counters[2]) + assertTrue { counters[0] > iterations / 4 } + assertTrue { counters[1] > iterations / 4 } + } +} 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 index fe69e97ba1..764f73602f 100644 --- 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 @@ -21,10 +21,7 @@ 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 +import kotlin.coroutines.experimental.* class DelayTest : TestBase() { /** @@ -47,7 +44,6 @@ class DelayTest : TestBase() { pool.shutdown() } - @Test fun testDelayWithoutDispatcher() = runBlocking(CoroutineName("testNoDispatcher.main")) { // launch w/o a specified dispatcher @@ -58,6 +54,21 @@ class DelayTest : TestBase() { assertThat(c.await(), IsEqual(42)) } + @Test + fun testNegativeDelay() = runBlocking { + expect(1) + val job = async(coroutineContext) { + expect(3) + delay(0) + expect(4) + } + + delay(-1) + expect(2) + job.await() + finish(5) + } + class CustomInterceptor(val pool: Executor) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { override fun interceptContinuation(continuation: Continuation): Continuation = Wrapper(pool, continuation) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt new file mode 100644 index 0000000000..594ef703b0 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt @@ -0,0 +1,59 @@ + +package kotlinx.coroutines.experimental + +import kotlin.test.* +import java.io.IOException + +class WithTimeoutTest : TestBase() { + @Test + fun testExceptionOnTimeout() = runTest { + expect(1) + try { + withTimeout(100) { + expect(2) + delay(1000) + expectUnreached() + "OK" + } + } catch (e: CancellationException) { + assertEquals("Timed out waiting for 100 MILLISECONDS", e.message) + finish(3) + } + } + + @Test + fun testSuppressExceptionWithResult() = runTest( + expected = { it is CancellationException } + ) { + expect(1) + val result = withTimeout(100) { + expect(2) + try { + delay(1000) + } catch (e: CancellationException) { + finish(3) + } + "OK" + } + expectUnreached() + } + + @Test + fun testSuppressExceptionWithAnotherException() = runTest( + expected = { it is IOException } + ) { + expect(1) + withTimeout(100) { + expect(2) + try { + delay(1000) + } catch (e: CancellationException) { + finish(3) + throw IOException(e) + } + expectUnreached() + "OK" + } + expectUnreached() + } +} \ No newline at end of file From 4dcdc4a10fa7be5411619bb532175459f1f510a0 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 12 Apr 2018 15:57:18 +0300 Subject: [PATCH 24/61] Remove duplicate test file after rebase --- .../experimental/WithTimeoutTest.kt | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt deleted file mode 100644 index 594ef703b0..0000000000 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt +++ /dev/null @@ -1,59 +0,0 @@ - -package kotlinx.coroutines.experimental - -import kotlin.test.* -import java.io.IOException - -class WithTimeoutTest : TestBase() { - @Test - fun testExceptionOnTimeout() = runTest { - expect(1) - try { - withTimeout(100) { - expect(2) - delay(1000) - expectUnreached() - "OK" - } - } catch (e: CancellationException) { - assertEquals("Timed out waiting for 100 MILLISECONDS", e.message) - finish(3) - } - } - - @Test - fun testSuppressExceptionWithResult() = runTest( - expected = { it is CancellationException } - ) { - expect(1) - val result = withTimeout(100) { - expect(2) - try { - delay(1000) - } catch (e: CancellationException) { - finish(3) - } - "OK" - } - expectUnreached() - } - - @Test - fun testSuppressExceptionWithAnotherException() = runTest( - expected = { it is IOException } - ) { - expect(1) - withTimeout(100) { - expect(2) - try { - delay(1000) - } catch (e: CancellationException) { - finish(3) - throw IOException(e) - } - expectUnreached() - "OK" - } - expectUnreached() - } -} \ No newline at end of file From d521478a1926aa1c0d4052ff9d1325ba38ca501a Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 13 Apr 2018 14:51:30 +0300 Subject: [PATCH 25/61] Separate Job (interface) and JobSupport (implementation). No other changes except making JobImpl internal (not private) --- .../kotlinx/coroutines/experimental/Job.kt | 978 ----------------- .../coroutines/experimental/JobSupport.kt | 984 ++++++++++++++++++ 2 files changed, 984 insertions(+), 978 deletions(-) create mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt index e94e951b29..0854e49728 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt @@ -19,13 +19,9 @@ package kotlinx.coroutines.experimental -import kotlinx.atomicfu.* -import kotlinx.coroutines.experimental.internal.* import kotlinx.coroutines.experimental.internalAnnotations.* -import kotlinx.coroutines.experimental.intrinsics.* import kotlinx.coroutines.experimental.selects.* import kotlin.coroutines.experimental.* -import kotlin.coroutines.experimental.intrinsics.* // --------------- core job interfaces --------------- @@ -481,977 +477,3 @@ public object NonDisposableHandle : DisposableHandle { /** 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.** - */ -internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 { - final override val key: CoroutineContext.Key<*> get() = Job - - /* - === Internal states === - - name state class public state description - ------ ------------ ------------ ----------- - EMPTY_N EmptyNew : New no listeners - EMPTY_A EmptyActive : Active no listeners - SINGLE JobNode : Active a single listener - SINGLE+ JobNode : Active a single listener + NodeList added as its next - LIST_N 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 : 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(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. - * @suppress **This is unstable API and it is subject to change.** - */ - internal fun initParentJobInternal(parent: Job?) { - check(parentHandle == null) - if (parent == null) { - parentHandle = NonDisposableHandle - return - } - parent.start() // make sure the parent is started - @Suppress("DEPRECATION") - val handle = parent.attachChild(this) - parentHandle = handle - // now check our state _after_ registering (see updateState order of actions) - if (isCompleted) { - handle.dispose() - parentHandle = NonDisposableHandle // release it just in case, to aid GC - } - } - - // ------------ state query ------------ - - /** - * Returns current state of this job. - * @suppress **This is unstable API and it is subject to change.** - */ - internal val state: Any? get() { - _state.loop { state -> // helper loop on state (complete in-progress atomic operations) - if (state !is OpDescriptor) return state - state.perform(this) - } - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal 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) - return true - } - - // when Job is in Cancelling state, it can only be promoted to Cancelled state, - // so if the proposed Update is not an appropriate Cancelled (preserving the cancellation cause), - // then the corresponding Cancelled state is constructed. - private fun coerceProposedUpdate(expect: Incomplete, proposedUpdate: Any?): Any? = - if (expect is Finishing && expect.cancelled != null && !isCorrespondinglyCancelled(expect.cancelled, proposedUpdate)) - createCancelled(expect.cancelled, proposedUpdate) else proposedUpdate - - private fun isCorrespondinglyCancelled(cancelled: Cancelled, proposedUpdate: Any?): Boolean { - if (proposedUpdate !is Cancelled) return false - // NOTE: equality comparison of causes is performed here by design, see equals of JobCancellationException - return proposedUpdate.cause == cancelled.cause || - proposedUpdate.cause is JobCancellationException && cancelled.cause == null - } - - private fun createCancelled(cancelled: Cancelled, proposedUpdate: Any?): Cancelled { - if (proposedUpdate !is CompletedExceptionally) return cancelled // not exception -- just use original cancelled - val exception = proposedUpdate.exception - if (cancelled.exception == exception) return cancelled // that is the cancelled we need already! - cancelled.cause?.let { exception.addSuppressedThrowable(it) } - return Cancelled(this, exception) - } - - /** - * 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?.let { - it.dispose() // volatile read parentHandle _after_ state was updated - parentHandle = NonDisposableHandle // release it just in case, to aid GC - } - 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) { - val exceptionally = update as? CompletedExceptionally - // Do overridable processing before completion handlers - if (!expect.isCancelling) onCancellationInternal(exceptionally) // only notify when was not cancelling before - onCompletionInternal(update, mode) - // Invoke completion handlers - 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) - } - } - - private inline fun > notifyHandlers(list: NodeList, cause: Throwable?) { - var exception: Throwable? = null - list.forEach { node -> - try { - node.invoke(cause) - } catch (ex: Throwable) { - exception?.apply { addSuppressedThrowable(ex) } ?: run { - exception = CompletionHandlerException("Exception in completion handler $node for $this", ex) - } - } - } - exception?.let { handleException(it) } - } - - private fun NodeList.notifyCompletion(cause: Throwable?) = - notifyHandlers>(this, cause) - - private fun notifyCancellation(list: NodeList, cause: Throwable?) = - notifyHandlers>(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 - onStartInternal() - return TRUE - } - is NodeList -> { // LIST -- a list of completion handlers (either new or active) - return state.tryMakeActive().also { result -> - if (result == TRUE) onStartInternal() - } - } - else -> return FALSE // not a new state - } - } - - /** - * Override to provide the actual [start] action. - * This function is invoked exactly once when non-active coroutine is [started][start]. - */ - internal open fun onStartInternal() {} - - public final override fun getCancellationException(): CancellationException { - 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 = - invokeOnCompletion(onCancelling = false, invokeImmediately = true, handler = handler) - - @Suppress("OverridingDeprecatedMember") - public final override fun invokeOnCompletion(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle = - invokeOnCompletion(onCancelling = onCancelling, invokeImmediately = true, handler = handler) - - @Suppress("OverridingDeprecatedMember") - public final override fun invokeOnCompletion(onCancelling_: Boolean, handler: CompletionHandler): DisposableHandle = - invokeOnCompletion(onCancelling = onCancelling_, invokeImmediately = true, handler = handler) - - // todo: non-final as a workaround for KT-21968, should be final in the future - public override fun invokeOnCompletion( - onCancelling: Boolean, - invokeImmediately: Boolean, - handler: CompletionHandler - ): DisposableHandle { - var nodeCache: JobNode<*>? = null - loopOnState { state -> - when (state) { - is Empty -> { // EMPTY_X state -- no completion handlers - if (state.isActive) { - // try move to SINGLE state - val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it } - if (_state.compareAndSet(state, node)) return node - } else - promoteEmptyToNodeList(state) // that way we can add listener for non-active coroutine - } - is Incomplete -> { - val list = state.list - if (list == null) { // SINGLE/SINGLE+ - promoteSingleToNodeList(state as JobNode<*>) - } else { - if (state is Finishing && state.cancelled != null && onCancelling) { - check(onCancelMode != ON_CANCEL_MAKE_CANCELLED) // cannot be in this state unless were support cancelling state - // installing cancellation handler on job that is being cancelled - if (invokeImmediately) handler(state.cancelled.cause) - return NonDisposableHandle - } - val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it } - if (addLastAtomic(state, list, node)) return node - } - } - else -> { // is complete - // :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, - // because we play type tricks on Kotlin/JS and handler is not necessarily a function there - if (invokeImmediately) handler.invokeIt((state as? CompletedExceptionally)?.cause) - return NonDisposableHandle - } - } - } - } - - private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode<*> { - val hasCancellingState = onCancelMode != ON_CANCEL_MAKE_CANCELLED - return if (onCancelling && hasCancellingState) - (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.nextNode // either our NodeList or somebody else won the race, updated state - // just attempt converting it to list if state is still the same, then we'll continue lock-free loop - _state.compareAndSet(state, list) - } - - public final override suspend fun join() { - if (!joinInternal()) { // fast-path no wait - 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 { cont -> - cont.disposeOnCompletion(invokeOnCompletion(handler = ResumeOnCompletion(this, cont).asHandler)) - } - - public final override val onJoin: SelectClause0 - get() = this - - // registerSelectJoin - public final override fun registerSelectClause0(select: SelectInstance, 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).asHandler)) - return - } - } - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal fun removeNode(node: JobNode<*>) { - // remove logic depends on the state of the job - loopOnState { state -> - when (state) { - is JobNode<*> -> { // SINGE/SINGLE+ state -- one completion handler - if (state !== node) return // a different job node --> we were already removed - // try remove and revert back to empty state - if (_state.compareAndSet(state, 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 - } - } - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal open val onCancelMode: Int get() = ON_CANCEL_MAKE_CANCELLING - - public override fun cancel(cause: Throwable?): Boolean = when (onCancelMode) { - ON_CANCEL_MAKE_CANCELLED -> makeCancelled(cause) - ON_CANCEL_MAKE_CANCELLING -> makeCancelling(cause) - ON_CANCEL_MAKE_COMPLETING -> makeCompletingOnCancel(cause) - else -> error("Invalid onCancelMode $onCancelMode") - } - - // 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 - onFinishingInternal(cancelled) - onCancellationInternal(cancelled) - notifyCancellation(list, cause) - return true - } - - private fun makeCompletingOnCancel(cause: Throwable?): Boolean = - makeCompleting(Cancelled(this, cause)) - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal fun makeCompleting(proposedUpdate: Any?): Boolean = - when (makeCompletingInternal(proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) { - COMPLETING_ALREADY_COMPLETING -> false - else -> 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 makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean = - when (makeCompletingInternal(proposedUpdate, mode)) { - COMPLETING_COMPLETED -> true - COMPLETING_WAITING_CHILDREN -> false - else -> throw IllegalStateException("Job $this is already complete or completing, " + - "but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull) - } - - private fun makeCompletingInternal(proposedUpdate: Any?, mode: Int): Int { - loopOnState { state -> - if (state !is Incomplete) - return COMPLETING_ALREADY_COMPLETING - if (state is Finishing && state.completing) - return COMPLETING_ALREADY_COMPLETING - val child: Child? = firstChild(state) ?: // or else complete immediately w/o children - when { - state !is Finishing && hasOnFinishingHandler(proposedUpdate) -> null // unless it has onFinishing handler - updateState(state, proposedUpdate, mode) -> return COMPLETING_COMPLETED - else -> return@loopOnState - } - val list = state.list ?: // must promote to list to correctly operate on child lists - when (state) { - is Empty -> { - promoteEmptyToNodeList(state) - return@loopOnState // retry - } - is JobNode<*> -> { - promoteSingleToNodeList(state) - return@loopOnState // retry - } - else -> error("Unexpected state with an empty list: $state") - } - // cancel all children in list on exceptional completion - if (proposedUpdate is CompletedExceptionally) - child?.cancelChildrenInternal(proposedUpdate.exception) - // switch to completing state - val cancelled = (state as? Finishing)?.cancelled ?: (proposedUpdate as? Cancelled) - val completing = Finishing(list, cancelled, true) - if (_state.compareAndSet(state, completing)) { - if (state !is Finishing) onFinishingInternal(proposedUpdate) - if (child != null && tryWaitForChild(child, proposedUpdate)) - return COMPLETING_WAITING_CHILDREN - if (updateState(completing, proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) - return COMPLETING_COMPLETED - } - } - } - - private tailrec fun Child.cancelChildrenInternal(cause: Throwable) { - childJob.cancel(JobCancellationException("Child job was cancelled because of parent failure", cause, childJob)) - nextChild()?.cancelChildrenInternal(cause) - } - - private val Any?.exceptionOrNull: Throwable? - get() = (this as? CompletedExceptionally)?.exception - - private fun firstChild(state: Incomplete) = - state as? Child ?: state.list?.nextChild() - - // return false when there is no more incomplete children to wait - private tailrec fun tryWaitForChild(child: Child, proposedUpdate: Any?): Boolean { - val handle = child.childJob.invokeOnCompletion(invokeImmediately = false, - handler = ChildCompletion(this, child, proposedUpdate).asHandler) - if (handle !== NonDisposableHandle) return true // child is not complete and we've started waiting for it - val nextChild = child.nextChild() ?: return false - return tryWaitForChild(nextChild, proposedUpdate) - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - 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() - // try wait for next child - if (waitChild != null && tryWaitForChild(waitChild, proposedUpdate)) return // waiting for next child - // no more children to wait -- try update state - if (updateState(state, proposedUpdate, MODE_ATOMIC_DEFAULT)) return - } - } - - private fun LockFreeLinkedListNode.nextChild(): Child? { - var cur = this - while (cur.isRemoved) cur = cur.prevNode // rollback to prev non-removed (or list head) - while (true) { - cur = cur.nextNode - if (cur.isRemoved) continue - if (cur is Child) return cur - if (cur is NodeList) return null // checked all -- no more children - } - } - - public final override val children: Sequence get() = buildSequence { - val state = this@JobSupport.state - when (state) { - is Child -> yield(state.childJob) - is Incomplete -> state.list?.let { list -> - list.forEach { yield(it.childJob) } - } - } - } - - @Suppress("OverridingDeprecatedMember") - public final override fun attachChild(child: Job): DisposableHandle = - invokeOnCompletion(onCancelling = true, handler = Child(this, child).asHandler) - - @Suppress("OverridingDeprecatedMember") - public final override fun cancelChildren(cause: Throwable?) { - this.cancelChildren(cause) // use extension function - } - - /** - * Override to process any exceptions that were encountered while invoking completion handlers - * installed via [invokeOnCompletion]. - * @suppress **This is unstable API and it is subject to change.** - */ - internal open fun handleException(exception: Throwable) { - throw exception - } - - /** - * This function 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.** - */ - internal open fun onCancellationInternal(exceptionally: CompletedExceptionally?) {} - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal open fun hasOnFinishingHandler(update: Any?) = false - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal open fun onFinishingInternal(update: Any?) {} - - /** - * 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.** - */ - internal open fun onCompletionInternal(state: Any?, mode: Int) {} - - // for nicer debugging - public override fun toString(): String = - "${nameString()}{${stateString()}}@$hexAddress" - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal open fun nameString(): String = classSimpleName - - 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" - } - } - - // 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 - - /* - * ================================================================================================= - * This is ready-to-use implementation for Deferred interface. - * However, it is not type-safe. Conceptually it just exposes the value of the underlying - * completed state as `Any?` - * ================================================================================================= - */ - - public val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally - - public fun getCompletionExceptionOrNull(): Throwable? { - val state = this.state - check(state !is Incomplete) { "This job has not completed yet" } - return state.exceptionOrNull - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal fun getCompletedInternal(): Any? { - val state = this.state - check(state !is Incomplete) { "This job has not completed yet" } - if (state is CompletedExceptionally) throw state.exception - return state - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - internal suspend fun awaitInternal(): Any? { - // fast-path -- check state (avoid extra object creation) - while(true) { // lock-free loop on state - val state = this.state - if (state !is Incomplete) { - // already complete -- just return result - if (state is CompletedExceptionally) 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) - }) - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - // registerSelectAwaitInternal - @Suppress("UNCHECKED_CAST") - internal fun registerSelectClause1Internal(select: SelectInstance, block: suspend (T) -> R) { - // fast-path -- check state and select/return if needed - loopOnState { state -> - if (select.isSelected) return - if (state !is Incomplete) { - // already complete -- select result - if (select.trySelect(null)) { - if (state is CompletedExceptionally) - select.resumeSelectCancellableWithException(state.exception) - else - block.startCoroutineUndispatched(state as T, select.completion) - } - return - } - if (startInternal(state) == 0) { - // slow-path -- register waiter for completion - select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(this, select, block).asHandler)) - return - } - } - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - @Suppress("UNCHECKED_CAST") - internal fun selectAwaitCompletion(select: SelectInstance, block: suspend (T) -> R) { - val state = this.state - // Note: await is non-atomic (can be cancelled while dispatched) - if (state is CompletedExceptionally) - select.resumeSelectCancellableWithException(state.exception) - else - block.startCoroutineCancellable(state as T, select.completion) - } -} - -internal const val ON_CANCEL_MAKE_CANCELLED = 0 -internal const val ON_CANCEL_MAKE_CANCELLING = 1 -internal const val ON_CANCEL_MAKE_COMPLETING = 2 - -private const val COMPLETING_ALREADY_COMPLETING = 0 -private const val COMPLETING_COMPLETED = 1 -private const val COMPLETING_WAITING_CHILDREN = 2 - -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) : Incomplete { - override val list: NodeList? get() = null - override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}" -} - -private class JobImpl(parent: Job? = null) : JobSupport(true) { - init { initParentJobInternal(parent) } - override val onCancelMode: Int get() = ON_CANCEL_MAKE_COMPLETING -} - -// -------- invokeOnCompletion nodes - -internal interface Incomplete { - val isActive: Boolean - val list: NodeList? // is null only for Empty and JobNode incomplete state objects -} - -internal abstract class JobNode actual constructor( - @JvmField val job: J -) : CompletionHandlerNode(), DisposableHandle, Incomplete { - override val isActive: Boolean get() = true - override val list: NodeList? get() = null - override fun dispose() = (job as JobSupport).removeNode(this) -} - -internal class NodeList( - 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 TRUE - return RETRY - } - - override fun toString(): String = buildString { - append("List") - append(if (isActive) "{Active}" else "{New}") - append("[") - var first = true - this@NodeList.forEach> { node -> - if (first) first = false else append(", ") - append(node) - } - append("]") - } -} - -private class InvokeOnCompletion( - job: Job, - private val handler: CompletionHandler -) : JobNode(job) { - override fun invoke(cause: Throwable?) = handler.invoke(cause) - override fun toString() = "InvokeOnCompletion[$classSimpleName@$hexAddress]" -} - -private class ResumeOnCompletion( - job: Job, - private val continuation: Continuation -) : JobNode(job) { - override fun invoke(cause: Throwable?) = continuation.resume(Unit) - override fun toString() = "ResumeOnCompletion[$continuation]" -} - -internal class DisposeOnCompletion( - job: Job, - private val handle: DisposableHandle -) : JobNode(job) { - override fun invoke(cause: Throwable?) = handle.dispose() - override fun toString(): String = "DisposeOnCompletion[$handle]" -} - -private class SelectJoinOnCompletion( - job: JobSupport, - private val select: SelectInstance, - private val block: suspend () -> R -) : JobNode(job) { - override fun invoke(cause: Throwable?) { - if (select.trySelect(null)) - block.startCoroutineCancellable(select.completion) - } - override fun toString(): String = "SelectJoinOnCompletion[$select]" -} - -private class SelectAwaitOnCompletion( - job: JobSupport, - private val select: SelectInstance, - private val block: suspend (T) -> R -) : JobNode(job) { - override fun invoke(cause: Throwable?) { - if (select.trySelect(null)) - job.selectAwaitCompletion(select, block) - } - override fun toString(): String = "SelectAwaitOnCompletion[$select]" -} - -// -------- invokeOnCancellation nodes - -/** - * Marker for node that shall be invoked on cancellation (in _cancelling_ state). - * **Note: may be invoked multiple times during cancellation.** - */ -internal abstract class JobCancellationNode(job: J) : JobNode(job) - -private class InvokeOnCancellation( - job: Job, - private val handler: CompletionHandler -) : JobCancellationNode(job) { - // delegate handler shall be invoked at most once, so here is an additional flag - private val _invoked = atomic(0) - override fun invoke(cause: Throwable?) { - if (_invoked.compareAndSet(0, 1)) handler.invoke(cause) - } - override fun toString() = "InvokeOnCancellation[$classSimpleName@$hexAddress]" -} - -internal class Child( - parent: JobSupport, - @JvmField val childJob: Job -) : JobCancellationNode(parent) { - override fun invoke(cause: Throwable?) { - // Always materialize the actual instance of parent's completion exception and cancel child with it - childJob.cancel(job.getCancellationException()) - } - override fun toString(): String = "Child[$childJob]" -} - -private class ChildCompletion( - private val parent: JobSupport, - private val child: Child, - private val proposedUpdate: Any? -) : JobNode(child.childJob) { - override fun invoke(cause: Throwable?) { - parent.continueCompleting(child, proposedUpdate) - } -} \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt new file mode 100644 index 0000000000..7898059684 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt @@ -0,0 +1,984 @@ +package kotlinx.coroutines.experimental + +import kotlinx.atomicfu.* +import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.* +import kotlinx.coroutines.experimental.intrinsics.* +import kotlinx.coroutines.experimental.selects.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +/** + * 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.** + */ +internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 { + final override val key: CoroutineContext.Key<*> get() = Job + + /* + === Internal states === + + name state class public state description + ------ ------------ ------------ ----------- + EMPTY_N EmptyNew : New no listeners + EMPTY_A EmptyActive : Active no listeners + SINGLE JobNode : Active a single listener + SINGLE+ JobNode : Active a single listener + NodeList added as its next + LIST_N 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 : 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(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. + * @suppress **This is unstable API and it is subject to change.** + */ + internal fun initParentJobInternal(parent: Job?) { + check(parentHandle == null) + if (parent == null) { + parentHandle = NonDisposableHandle + return + } + parent.start() // make sure the parent is started + @Suppress("DEPRECATION") + val handle = parent.attachChild(this) + parentHandle = handle + // now check our state _after_ registering (see updateState order of actions) + if (isCompleted) { + handle.dispose() + parentHandle = NonDisposableHandle // release it just in case, to aid GC + } + } + + // ------------ state query ------------ + + /** + * Returns current state of this job. + * @suppress **This is unstable API and it is subject to change.** + */ + internal val state: Any? get() { + _state.loop { state -> // helper loop on state (complete in-progress atomic operations) + if (state !is OpDescriptor) return state + state.perform(this) + } + } + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal 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) + return true + } + + // when Job is in Cancelling state, it can only be promoted to Cancelled state, + // so if the proposed Update is not an appropriate Cancelled (preserving the cancellation cause), + // then the corresponding Cancelled state is constructed. + private fun coerceProposedUpdate(expect: Incomplete, proposedUpdate: Any?): Any? = + if (expect is Finishing && expect.cancelled != null && !isCorrespondinglyCancelled(expect.cancelled, proposedUpdate)) + createCancelled(expect.cancelled, proposedUpdate) else proposedUpdate + + private fun isCorrespondinglyCancelled(cancelled: Cancelled, proposedUpdate: Any?): Boolean { + if (proposedUpdate !is Cancelled) return false + // NOTE: equality comparison of causes is performed here by design, see equals of JobCancellationException + return proposedUpdate.cause == cancelled.cause || + proposedUpdate.cause is JobCancellationException && cancelled.cause == null + } + + private fun createCancelled(cancelled: Cancelled, proposedUpdate: Any?): Cancelled { + if (proposedUpdate !is CompletedExceptionally) return cancelled // not exception -- just use original cancelled + val exception = proposedUpdate.exception + if (cancelled.exception == exception) return cancelled // that is the cancelled we need already! + cancelled.cause?.let { exception.addSuppressedThrowable(it) } + return Cancelled(this, exception) + } + + /** + * 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?.let { + it.dispose() // volatile read parentHandle _after_ state was updated + parentHandle = NonDisposableHandle // release it just in case, to aid GC + } + 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) { + val exceptionally = update as? CompletedExceptionally + // Do overridable processing before completion handlers + if (!expect.isCancelling) onCancellationInternal(exceptionally) // only notify when was not cancelling before + onCompletionInternal(update, mode) + // Invoke completion handlers + 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) + } + } + + private inline fun > notifyHandlers(list: NodeList, cause: Throwable?) { + var exception: Throwable? = null + list.forEach { node -> + try { + node.invoke(cause) + } catch (ex: Throwable) { + exception?.apply { addSuppressedThrowable(ex) } ?: run { + exception = CompletionHandlerException("Exception in completion handler $node for $this", ex) + } + } + } + exception?.let { handleException(it) } + } + + private fun NodeList.notifyCompletion(cause: Throwable?) = + notifyHandlers>(this, cause) + + private fun notifyCancellation(list: NodeList, cause: Throwable?) = + notifyHandlers>(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 + onStartInternal() + return TRUE + } + is NodeList -> { // LIST -- a list of completion handlers (either new or active) + return state.tryMakeActive().also { result -> + if (result == TRUE) onStartInternal() + } + } + else -> return FALSE // not a new state + } + } + + /** + * Override to provide the actual [start] action. + * This function is invoked exactly once when non-active coroutine is [started][start]. + */ + internal open fun onStartInternal() {} + + public final override fun getCancellationException(): CancellationException { + 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 = + invokeOnCompletion(onCancelling = false, invokeImmediately = true, handler = handler) + + @Suppress("OverridingDeprecatedMember") + public final override fun invokeOnCompletion(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle = + invokeOnCompletion(onCancelling = onCancelling, invokeImmediately = true, handler = handler) + + @Suppress("OverridingDeprecatedMember") + public final override fun invokeOnCompletion(onCancelling_: Boolean, handler: CompletionHandler): DisposableHandle = + invokeOnCompletion(onCancelling = onCancelling_, invokeImmediately = true, handler = handler) + + // todo: non-final as a workaround for KT-21968, should be final in the future + public override fun invokeOnCompletion( + onCancelling: Boolean, + invokeImmediately: Boolean, + handler: CompletionHandler + ): DisposableHandle { + var nodeCache: JobNode<*>? = null + loopOnState { state -> + when (state) { + is Empty -> { // EMPTY_X state -- no completion handlers + if (state.isActive) { + // try move to SINGLE state + val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it } + if (_state.compareAndSet(state, node)) return node + } else + promoteEmptyToNodeList(state) // that way we can add listener for non-active coroutine + } + is Incomplete -> { + val list = state.list + if (list == null) { // SINGLE/SINGLE+ + promoteSingleToNodeList(state as JobNode<*>) + } else { + if (state is Finishing && state.cancelled != null && onCancelling) { + check(onCancelMode != ON_CANCEL_MAKE_CANCELLED) // cannot be in this state unless were support cancelling state + // installing cancellation handler on job that is being cancelled + if (invokeImmediately) handler(state.cancelled.cause) + return NonDisposableHandle + } + val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it } + if (addLastAtomic(state, list, node)) return node + } + } + else -> { // is complete + // :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, + // because we play type tricks on Kotlin/JS and handler is not necessarily a function there + if (invokeImmediately) handler.invokeIt((state as? CompletedExceptionally)?.cause) + return NonDisposableHandle + } + } + } + } + + private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode<*> { + val hasCancellingState = onCancelMode != ON_CANCEL_MAKE_CANCELLED + return if (onCancelling && hasCancellingState) + (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.nextNode // either our NodeList or somebody else won the race, updated state + // just attempt converting it to list if state is still the same, then we'll continue lock-free loop + _state.compareAndSet(state, list) + } + + public final override suspend fun join() { + if (!joinInternal()) { // fast-path no wait + 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 { cont -> + cont.disposeOnCompletion(invokeOnCompletion(handler = ResumeOnCompletion(this, cont).asHandler)) + } + + public final override val onJoin: SelectClause0 + get() = this + + // registerSelectJoin + public final override fun registerSelectClause0(select: SelectInstance, 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).asHandler)) + return + } + } + } + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal fun removeNode(node: JobNode<*>) { + // remove logic depends on the state of the job + loopOnState { state -> + when (state) { + is JobNode<*> -> { // SINGE/SINGLE+ state -- one completion handler + if (state !== node) return // a different job node --> we were already removed + // try remove and revert back to empty state + if (_state.compareAndSet(state, 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 + } + } + } + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal open val onCancelMode: Int get() = ON_CANCEL_MAKE_CANCELLING + + public override fun cancel(cause: Throwable?): Boolean = when (onCancelMode) { + ON_CANCEL_MAKE_CANCELLED -> makeCancelled(cause) + ON_CANCEL_MAKE_CANCELLING -> makeCancelling(cause) + ON_CANCEL_MAKE_COMPLETING -> makeCompletingOnCancel(cause) + else -> error("Invalid onCancelMode $onCancelMode") + } + + // 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 + onFinishingInternal(cancelled) + onCancellationInternal(cancelled) + notifyCancellation(list, cause) + return true + } + + private fun makeCompletingOnCancel(cause: Throwable?): Boolean = + makeCompleting(Cancelled(this, cause)) + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal fun makeCompleting(proposedUpdate: Any?): Boolean = + when (makeCompletingInternal(proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) { + COMPLETING_ALREADY_COMPLETING -> false + else -> 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 makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean = + when (makeCompletingInternal(proposedUpdate, mode)) { + COMPLETING_COMPLETED -> true + COMPLETING_WAITING_CHILDREN -> false + else -> throw IllegalStateException("Job $this is already complete or completing, " + + "but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull) + } + + private fun makeCompletingInternal(proposedUpdate: Any?, mode: Int): Int { + loopOnState { state -> + if (state !is Incomplete) + return COMPLETING_ALREADY_COMPLETING + if (state is Finishing && state.completing) + return COMPLETING_ALREADY_COMPLETING + val child: Child? = firstChild(state) ?: // or else complete immediately w/o children + when { + state !is Finishing && hasOnFinishingHandler(proposedUpdate) -> null // unless it has onFinishing handler + updateState(state, proposedUpdate, mode) -> return COMPLETING_COMPLETED + else -> return@loopOnState + } + val list = state.list ?: // must promote to list to correctly operate on child lists + when (state) { + is Empty -> { + promoteEmptyToNodeList(state) + return@loopOnState // retry + } + is JobNode<*> -> { + promoteSingleToNodeList(state) + return@loopOnState // retry + } + else -> error("Unexpected state with an empty list: $state") + } + // cancel all children in list on exceptional completion + if (proposedUpdate is CompletedExceptionally) + child?.cancelChildrenInternal(proposedUpdate.exception) + // switch to completing state + val cancelled = (state as? Finishing)?.cancelled ?: (proposedUpdate as? Cancelled) + val completing = Finishing(list, cancelled, true) + if (_state.compareAndSet(state, completing)) { + if (state !is Finishing) onFinishingInternal(proposedUpdate) + if (child != null && tryWaitForChild(child, proposedUpdate)) + return COMPLETING_WAITING_CHILDREN + if (updateState(completing, proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) + return COMPLETING_COMPLETED + } + } + } + + private tailrec fun Child.cancelChildrenInternal(cause: Throwable) { + childJob.cancel(JobCancellationException("Child job was cancelled because of parent failure", cause, childJob)) + nextChild()?.cancelChildrenInternal(cause) + } + + private val Any?.exceptionOrNull: Throwable? + get() = (this as? CompletedExceptionally)?.exception + + private fun firstChild(state: Incomplete) = + state as? Child ?: state.list?.nextChild() + + // return false when there is no more incomplete children to wait + private tailrec fun tryWaitForChild(child: Child, proposedUpdate: Any?): Boolean { + val handle = child.childJob.invokeOnCompletion(invokeImmediately = false, + handler = ChildCompletion(this, child, proposedUpdate).asHandler) + if (handle !== NonDisposableHandle) return true // child is not complete and we've started waiting for it + val nextChild = child.nextChild() ?: return false + return tryWaitForChild(nextChild, proposedUpdate) + } + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + 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() + // try wait for next child + if (waitChild != null && tryWaitForChild(waitChild, proposedUpdate)) return // waiting for next child + // no more children to wait -- try update state + if (updateState(state, proposedUpdate, MODE_ATOMIC_DEFAULT)) return + } + } + + private fun LockFreeLinkedListNode.nextChild(): Child? { + var cur = this + while (cur.isRemoved) cur = cur.prevNode // rollback to prev non-removed (or list head) + while (true) { + cur = cur.nextNode + if (cur.isRemoved) continue + if (cur is Child) return cur + if (cur is NodeList) return null // checked all -- no more children + } + } + + public final override val children: Sequence get() = buildSequence { + val state = this@JobSupport.state + when (state) { + is Child -> yield(state.childJob) + is Incomplete -> state.list?.let { list -> + list.forEach { yield(it.childJob) } + } + } + } + + @Suppress("OverridingDeprecatedMember") + public final override fun attachChild(child: Job): DisposableHandle = + invokeOnCompletion(onCancelling = true, handler = Child(this, child).asHandler) + + @Suppress("OverridingDeprecatedMember") + public final override fun cancelChildren(cause: Throwable?) { + this.cancelChildren(cause) // use extension function + } + + /** + * Override to process any exceptions that were encountered while invoking completion handlers + * installed via [invokeOnCompletion]. + * @suppress **This is unstable API and it is subject to change.** + */ + internal open fun handleException(exception: Throwable) { + throw exception + } + + /** + * This function 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.** + */ + internal open fun onCancellationInternal(exceptionally: CompletedExceptionally?) {} + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal open fun hasOnFinishingHandler(update: Any?) = false + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal open fun onFinishingInternal(update: Any?) {} + + /** + * 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.** + */ + internal open fun onCompletionInternal(state: Any?, mode: Int) {} + + // for nicer debugging + public override fun toString(): String = + "${nameString()}{${stateString()}}@$hexAddress" + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal open fun nameString(): String = classSimpleName + + 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" + } + } + + // 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 + + /* + * ================================================================================================= + * This is ready-to-use implementation for Deferred interface. + * However, it is not type-safe. Conceptually it just exposes the value of the underlying + * completed state as `Any?` + * ================================================================================================= + */ + + public val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally + + public fun getCompletionExceptionOrNull(): Throwable? { + val state = this.state + check(state !is Incomplete) { "This job has not completed yet" } + return state.exceptionOrNull + } + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal fun getCompletedInternal(): Any? { + val state = this.state + check(state !is Incomplete) { "This job has not completed yet" } + if (state is CompletedExceptionally) throw state.exception + return state + } + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + internal suspend fun awaitInternal(): Any? { + // fast-path -- check state (avoid extra object creation) + while(true) { // lock-free loop on state + val state = this.state + if (state !is Incomplete) { + // already complete -- just return result + if (state is CompletedExceptionally) 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) + }) + } + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + // registerSelectAwaitInternal + @Suppress("UNCHECKED_CAST") + internal fun registerSelectClause1Internal(select: SelectInstance, block: suspend (T) -> R) { + // fast-path -- check state and select/return if needed + loopOnState { state -> + if (select.isSelected) return + if (state !is Incomplete) { + // already complete -- select result + if (select.trySelect(null)) { + if (state is CompletedExceptionally) + select.resumeSelectCancellableWithException(state.exception) + else + block.startCoroutineUndispatched(state as T, select.completion) + } + return + } + if (startInternal(state) == 0) { + // slow-path -- register waiter for completion + select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(this, select, block).asHandler)) + return + } + } + } + + /** + * @suppress **This is unstable API and it is subject to change.** + */ + @Suppress("UNCHECKED_CAST") + internal fun selectAwaitCompletion(select: SelectInstance, block: suspend (T) -> R) { + val state = this.state + // Note: await is non-atomic (can be cancelled while dispatched) + if (state is CompletedExceptionally) + select.resumeSelectCancellableWithException(state.exception) + else + block.startCoroutineCancellable(state as T, select.completion) + } +} + +// --------------- helper classes to simplify job implementation + + +internal const val ON_CANCEL_MAKE_CANCELLED = 0 +internal const val ON_CANCEL_MAKE_CANCELLING = 1 +internal const val ON_CANCEL_MAKE_COMPLETING = 2 + +private const val COMPLETING_ALREADY_COMPLETING = 0 +private const val COMPLETING_COMPLETED = 1 +private const val COMPLETING_WAITING_CHILDREN = 2 + +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) : Incomplete { + override val list: NodeList? get() = null + override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}" +} + +internal class JobImpl(parent: Job? = null) : JobSupport(true) { + init { initParentJobInternal(parent) } + override val onCancelMode: Int get() = ON_CANCEL_MAKE_COMPLETING +} + +// -------- invokeOnCompletion nodes + +internal interface Incomplete { + val isActive: Boolean + val list: NodeList? // is null only for Empty and JobNode incomplete state objects +} + +internal abstract class JobNode actual constructor( + @JvmField val job: J +) : CompletionHandlerNode(), DisposableHandle, Incomplete { + override val isActive: Boolean get() = true + override val list: NodeList? get() = null + override fun dispose() = (job as JobSupport).removeNode(this) +} + +internal class NodeList( + 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 TRUE + return RETRY + } + + override fun toString(): String = buildString { + append("List") + append(if (isActive) "{Active}" else "{New}") + append("[") + var first = true + this@NodeList.forEach> { node -> + if (first) first = false else append(", ") + append(node) + } + append("]") + } +} + +private class InvokeOnCompletion( + job: Job, + private val handler: CompletionHandler +) : JobNode(job) { + override fun invoke(cause: Throwable?) = handler.invoke(cause) + override fun toString() = "InvokeOnCompletion[$classSimpleName@$hexAddress]" +} + +private class ResumeOnCompletion( + job: Job, + private val continuation: Continuation +) : JobNode(job) { + override fun invoke(cause: Throwable?) = continuation.resume(Unit) + override fun toString() = "ResumeOnCompletion[$continuation]" +} + +internal class DisposeOnCompletion( + job: Job, + private val handle: DisposableHandle +) : JobNode(job) { + override fun invoke(cause: Throwable?) = handle.dispose() + override fun toString(): String = "DisposeOnCompletion[$handle]" +} + +private class SelectJoinOnCompletion( + job: JobSupport, + private val select: SelectInstance, + private val block: suspend () -> R +) : JobNode(job) { + override fun invoke(cause: Throwable?) { + if (select.trySelect(null)) + block.startCoroutineCancellable(select.completion) + } + override fun toString(): String = "SelectJoinOnCompletion[$select]" +} + +private class SelectAwaitOnCompletion( + job: JobSupport, + private val select: SelectInstance, + private val block: suspend (T) -> R +) : JobNode(job) { + override fun invoke(cause: Throwable?) { + if (select.trySelect(null)) + job.selectAwaitCompletion(select, block) + } + override fun toString(): String = "SelectAwaitOnCompletion[$select]" +} + +// -------- invokeOnCancellation nodes + +/** + * Marker for node that shall be invoked on cancellation (in _cancelling_ state). + * **Note: may be invoked multiple times during cancellation.** + */ +internal abstract class JobCancellationNode(job: J) : JobNode(job) + +private class InvokeOnCancellation( + job: Job, + private val handler: CompletionHandler +) : JobCancellationNode(job) { + // delegate handler shall be invoked at most once, so here is an additional flag + private val _invoked = atomic(0) + override fun invoke(cause: Throwable?) { + if (_invoked.compareAndSet(0, 1)) handler.invoke(cause) + } + override fun toString() = "InvokeOnCancellation[$classSimpleName@$hexAddress]" +} + +internal class Child( + parent: JobSupport, + @JvmField val childJob: Job +) : JobCancellationNode(parent) { + override fun invoke(cause: Throwable?) { + // Always materialize the actual instance of parent's completion exception and cancel child with it + childJob.cancel(job.getCancellationException()) + } + override fun toString(): String = "Child[$childJob]" +} + +private class ChildCompletion( + private val parent: JobSupport, + private val child: Child, + private val proposedUpdate: Any? +) : JobNode(child.childJob) { + override fun invoke(cause: Throwable?) { + parent.continueCompleting(child, proposedUpdate) + } +} From 931587a5c38d50bc2d5635e290f742296a0d44bf Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 16 Apr 2018 17:51:12 +0300 Subject: [PATCH 26/61] Improve test coverage of CancellableCoroutine, add benchmark --- benchmarks/build.gradle | 1 + .../CancellableContinuationBenchmark.kt | 52 ++++++++ .../experimental/NonCancellableTest.kt | 125 ++++++++++++++++++ .../experimental/WithContextTest.kt | 32 ++++- .../coroutines/experimental/JoinStressTest.kt | 104 +++++++++++++++ 5 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 benchmarks/src/jmh/kotlin/benchmarks/CancellableContinuationBenchmark.kt create mode 100644 common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/NonCancellableTest.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JoinStressTest.kt diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index ff799531f6..3e06ebaa4c 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -8,6 +8,7 @@ repositories { dependencies { jmh 'com.typesafe.akka:akka-actor:2.0.2' + jmh project(':kotlinx-coroutines-core-common') jmh project(':kotlinx-coroutines-core') jmh project(':kotlinx-coroutines-core').sourceSets.test.output jmh project(':kotlinx-coroutines-io') diff --git a/benchmarks/src/jmh/kotlin/benchmarks/CancellableContinuationBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/CancellableContinuationBenchmark.kt new file mode 100644 index 0000000000..ff7f003f22 --- /dev/null +++ b/benchmarks/src/jmh/kotlin/benchmarks/CancellableContinuationBenchmark.kt @@ -0,0 +1,52 @@ +package benchmarks + +import kotlinx.coroutines.experimental.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +@Warmup(iterations = 5) +@Measurement(iterations = 10) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Fork(2) +open class CancellableContinuationBenchmark { + + @Benchmark + fun awaitWithSuspension(): Int { + val deferred = CompletableDeferred() + return run(allowSuspend = true) { deferred.await() } + } + + @Benchmark + fun awaitNoSuspension(): Int { + val deferred = CompletableDeferred(1) + return run { deferred.await() } + } + + private fun run(allowSuspend: Boolean = false, block: suspend () -> Int): Int { + val value = block.startCoroutineUninterceptedOrReturn(EmptyContinuation) + if (value === COROUTINE_SUSPENDED) { + if (!allowSuspend) { + throw IllegalStateException("Unexpected suspend") + } else { + return -1 + } + } + + return value as Int + } + + object EmptyContinuation : Continuation { + override val context: CoroutineContext + get() = EmptyCoroutineContext + + override fun resume(value: Int) { + } + + override fun resumeWithException(exception: Throwable) { + } + } +} diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/NonCancellableTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/NonCancellableTest.kt new file mode 100644 index 0000000000..9c15d29525 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/NonCancellableTest.kt @@ -0,0 +1,125 @@ +package kotlinx.coroutines.experimental + +import kotlin.coroutines.experimental.* +import kotlin.test.* + +class NonCancellableTest : TestBase() { + + @Test + fun testNonCancellable() = runTest { + expect(1) + val job = async(coroutineContext) { + withContext(NonCancellable) { + expect(2) + yield() + expect(4) + } + + expect(5) + yield() + expectUnreached() + } + + yield() + job.cancel() + expect(3) + assertTrue(job.isCancelled) + try { + job.await() + expectUnreached() + } catch (e: JobCancellationException) { + assertNull(e.cause) + finish(6) + } + } + + @Test + fun testNonCancellableWithException() = runTest { + expect(1) + val job = async(coroutineContext) { + withContext(NonCancellable) { + expect(2) + yield() + expect(4) + } + + expect(5) + yield() + expectUnreached() + } + + yield() + job.cancel(NumberFormatException()) + expect(3) + assertTrue(job.isCancelled) + try { + job.await() + expectUnreached() + } catch (e: JobCancellationException) { + assertTrue(e.cause is NumberFormatException) + finish(6) + } + } + + @Test + fun testNonCancellableFinally() = runTest { + expect(1) + val job = async(coroutineContext) { + try { + expect(2) + yield() + expectUnreached() + } finally { + withContext(NonCancellable) { + expect(4) + yield() + expect(5) + } + } + + expectUnreached() + } + + yield() + job.cancel() + expect(3) + assertTrue(job.isCancelled) + + try { + job.await() + expectUnreached() + } catch (e: JobCancellationException) { + finish(6) + } + } + + @Test + fun testNonCancellableTwice() = runTest { + expect(1) + val job = async(coroutineContext) { + withContext(NonCancellable) { + expect(2) + yield() + expect(4) + } + + withContext(NonCancellable) { + expect(5) + yield() + expect(6) + } + } + + yield() + job.cancel() + expect(3) + assertTrue(job.isCancelled) + try { + job.await() + expectUnreached() + } catch (e: JobCancellationException) { + assertNull(e.cause) + finish(7) + } + } +} diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt index 2679ad58a1..bcd0c3a1c4 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt @@ -23,6 +23,7 @@ import kotlin.coroutines.experimental.* import kotlin.test.* class WithContextTest : TestBase() { + @Test fun testSameContextNoSuspend() = runTest { expect(1) @@ -145,7 +146,7 @@ class WithContextTest : TestBase() { } @Test - fun testRunWithException() = runTest { + fun testRunSelfCancellationWithException() = runTest { expect(1) var job: Job? = null job = launch(coroutineContext) { @@ -171,6 +172,33 @@ class WithContextTest : TestBase() { finish(8) } + @Test + fun testRunSelfCancellation() = runTest { + expect(1) + var job: Job? = null + job = launch(coroutineContext) { + try { + expect(3) + withContext(wrapperDispatcher(coroutineContext)) { + expect(5) + job!!.cancel() // cancel itself + } + } catch (e: Throwable) { + expect(6) + // make sure TestException, not CancellationException is thrown! + assertTrue(e is JobCancellationException, "Caught $e") + } + } + + expect(2) + yield() // to the launched job + expect(4) + yield() // again to the job + expect(6) + yield() // again to exception handler + finish(8) + } + private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext { val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher return object : CoroutineDispatcher() { @@ -181,4 +209,4 @@ class WithContextTest : TestBase() { } private class TestException : Exception() -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JoinStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JoinStressTest.kt new file mode 100644 index 0000000000..d58f27f252 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JoinStressTest.kt @@ -0,0 +1,104 @@ +package kotlinx.coroutines.experimental + +import org.junit.* +import org.junit.Test +import java.io.* +import java.util.concurrent.* +import kotlin.test.* + +class JoinStressTest : TestBase() { + + val iterations = 50_000 * stressTestMultiplier + + private val pool = newFixedThreadPoolContext(3, "JoinStressTest") + + @After + fun tearDown() { + pool.close() + } + + class TestException : Exception() { + override fun fillInStackTrace(): Throwable = this + } + + @Test + fun testExceptionalJoinWithCancellation() = runBlocking { + val results = IntArray(2) + + repeat(iterations) { + val barrier = CyclicBarrier(3) + val exceptionalJob = async(pool) { + barrier.await() + throw TestException() + } + + + val awaiterJob = async(pool) { + barrier.await() + try { + exceptionalJob.await() + } catch (e: TestException) { + 0 + } catch (e: CancellationException) { + 1 + } + } + + barrier.await() + exceptionalJob.cancel() + ++results[awaiterJob.await()] + require(!exceptionalJob.cancel()) + } + + // Check that concurrent cancellation of job which throws TestException without suspends doesn't suppress TestException + assertEquals(iterations, results[0], results.toList().toString()) + assertEquals(0, results[1], results.toList().toString()) + } + + @Test + fun testExceptionalJoinWithMultipleCancellations() = runBlocking { + val results = IntArray(2) + var successfulCancellations = 0 + + repeat(iterations) { + val barrier = CyclicBarrier(4) + val exceptionalJob = async(pool) { + barrier.await() + throw TestException() + } + + val awaiterJob = async(pool) { + barrier.await() + try { + exceptionalJob.await() + } catch (e: TestException) { + 0 + } catch (e: CancellationException) { + 1 + } + } + + val canceller = async(pool) { + barrier.await() + exceptionalJob.cancel(IOException()) + } + + barrier.await() + val result = exceptionalJob.cancel() + ++results[awaiterJob.await()] + val cancellerResult = canceller.await() + + // None or one cancel can succeed + require(!(result && cancellerResult)) + require(!exceptionalJob.cancel()) + + if (result || cancellerResult) { + ++successfulCancellations + } + } + + assertEquals(iterations, results[0], results.toList().toString()) + assertEquals(0, results[1], results.toList().toString()) + require(successfulCancellations > 0) { "Cancellation never succeeds, something wrong with stress test infra" } + } +} From f3a501394545bacc47096ccef7470acdfce88678 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 16 Apr 2018 19:41:20 +0300 Subject: [PATCH 27/61] Decouple AbstractContinuation and CancellableContinuation from Job interface --- .../experimental/AbstractContinuation.kt | 253 ++++++++++++++++-- .../experimental/CancellableContinuation.kt | 126 ++++++--- .../experimental/CompletedExceptionally.kt | 20 +- .../experimental/CompletionHandler.common.kt | 8 +- .../coroutines/experimental/JobSupport.kt | 43 +-- .../CancellableContinuationHandlersTest.kt | 90 +++++++ .../experimental/WithContextTest.kt | 6 +- .../experimental/CompletionHandler.kt | 5 + .../kotlinx/coroutines/experimental/Future.kt | 49 +++- .../experimental/CompletionHandler.kt | 6 + 10 files changed, 527 insertions(+), 79 deletions(-) create mode 100644 common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CancellableContinuationHandlersTest.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt index 67a7fc64e7..c76e304e82 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt @@ -17,6 +17,7 @@ package kotlinx.coroutines.experimental import kotlinx.atomicfu.* +import kotlinx.coroutines.experimental.internalAnnotations.* import kotlin.coroutines.experimental.* import kotlin.coroutines.experimental.intrinsics.* @@ -30,8 +31,19 @@ private const val RESUMED = 2 internal abstract class AbstractContinuation( public final override val delegate: Continuation, public final override val resumeMode: Int -) : JobSupport(true), Continuation, DispatchedTask { - private val _decision = atomic(UNDECIDED) +) : Continuation, DispatchedTask { + + /* + * AbstractContinuation is a subset of Job with following limitations: + * 1) It can have only cancellation listeners + * 2) It always invokes cancellation listener if it's cancelled (no 'invokeImmediately') + * 3) It can have at most one cancellation listener + * 4) It cannot be in cancelling state, only active/finished/cancelled + * 5) Its cancellation listeners cannot be deregistered + * + * As a consequence it has much simpler state machine, more lightweight machinery and + * less dependencies. + */ /* decision state machine @@ -47,9 +59,56 @@ internal abstract class AbstractContinuation( Note: both tryResume and trySuspend can be invoked at most once, first invocation wins */ + private val _decision = atomic(UNDECIDED) + + /* + === Internal states === + name state class public state description + ------ ------------ ------------ ----------- + ACTIVE Active : Active active, no listeners + SINGLE_A CancellationHandler : Active active, one cancellation listener + CANCELLED Cancelled : Cancelled cancelled (final state) + COMPLETED any : Completed produced some result or threw an exception (final state) + */ + private val _state = atomic(ACTIVE) + + @Volatile + private var parentHandle: DisposableHandle? = null + + internal val state: Any? get() = _state.value + + public val isActive: Boolean get() = state is NotCompleted + + public val isCompleted: Boolean get() = state !is NotCompleted + + public val isCancelled: Boolean get() = state is CancelledContinuation + + internal fun initParentJobInternal(parent: Job?) { + check(parentHandle == null) + if (parent == null) { + parentHandle = NonDisposableHandle + return + } + parent.start() // make sure the parent is started + val handle = parent.invokeOnCompletion(onCancelling = true, handler = ChildContinuation(parent, this).asHandler) + + parentHandle = handle + // now check our state _after_ registering (see updateState order of actions) + if (isCompleted) { + handle.dispose() + parentHandle = NonDisposableHandle // release it just in case, to aid GC + } + } override fun takeState(): Any? = state + public fun cancel(cause: Throwable?): Boolean { + loopOnState { state -> + if (state !is NotCompleted) return false // quit if already complete + if (updateStateCancelled(state, cause)) return true + } + } + private fun trySuspend(): Boolean { _decision.loop { decision -> when (decision) { @@ -79,36 +138,196 @@ internal abstract class AbstractContinuation( return getSuccessfulResult(state) } - internal final override fun onCompletionInternal(state: Any?, mode: Int) { - if (tryResume()) return // completed before getResult invocation -- bail out - // otherwise, getResult has already commenced, i.e. completed later or in other thread - dispatch(mode) - } - override fun resume(value: T) = resumeImpl(value, resumeMode) override fun resumeWithException(exception: Throwable) = resumeImpl(CompletedExceptionally(exception), resumeMode) + public fun invokeOnCancellation(handler: CompletionHandler) { + var handleCache: CancellationHandler? = null + loopOnState { state -> + when (state) { + is Active -> { + val node = handleCache ?: makeHandler(handler).also { handleCache = it } + if (_state.compareAndSet(state, node)) { + return + } + } + is CancellationHandler -> error("It's prohibited to register multiple handlers, tried to register $handler, already has $state") + is CancelledContinuation -> { + /* + * Continuation is complete, invoke directly. + * NOTE: multiple invokeOnCancellation calls with different handlers are allowed on completed coroutine. + * It's slightly inconsistent with running coroutine, but currently, we have no mechanism to check + * whether any handler was registered during continuation lifecycle without additional overhead. + * This may be changed in the future. + * + * :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, + * because we play type tricks on Kotlin/JS and handler is not necessarily a function there + */ + handler.invokeIt((state as? CompletedExceptionally)?.cause) + return + } + else -> return + } + } + } + + private fun updateStateCancelled(state: NotCompleted, cause: Throwable?): Boolean = + updateState(state, CancelledContinuation(this, cause), mode = MODE_ATOMIC_DEFAULT) + + private fun onCompletionInternal(mode: Int) { + if (tryResume()) return // completed before getResult invocation -- bail out + // otherwise, getResult has already commenced, i.e. completed later or in other thread + dispatch(mode) + } + + protected inline fun loopOnState(block: (Any?) -> Unit): Nothing { + while (true) { + block(state) + } + } + protected fun resumeImpl(proposedUpdate: Any?, resumeMode: Int) { loopOnState { state -> when (state) { - is Incomplete -> { + is NotCompleted -> { if (updateState(state, proposedUpdate, resumeMode)) return } - is Cancelled -> { - // Ignore resumes in cancelled coroutines, but handle exception if a different one here - if (proposedUpdate is CompletedExceptionally && proposedUpdate.exception != state.exception) - handleException(proposedUpdate.exception) - return + + is CancelledContinuation -> { + if (proposedUpdate !is CompletedExceptionally) { + return // Cancelled continuation completed, do nothing + } + + /* + * Coerce concurrent cancellation and pending thrown exception. + * E.g. for linear history `T1: cancel() T2 (job): throw e T3: job.await()` + * we'd like to see actual exception in T3, not JobCancellationException. + * So thrown exception overwrites cancellation exception, but + * suppresses its non-null cause. + */ + if (state.exception is CancellationException && state.exception.cause == null) { + return // Do not add to suppressed regular cancellation + } + + if (state.exception is CancellationException && state.exception.cause === proposedUpdate.exception) { + return // Do not add to suppressed cancellation with the same cause + } + + if (state.exception === proposedUpdate.exception) { + return // Do no add to suppressed the same exception + } + + val exception = proposedUpdate.exception + val update = CompletedExceptionally(exception) + if (state.cause != null) { + exception.addSuppressedThrowable(state.cause) + } + + if (_state.compareAndSet(state, update)) { + return + } } - else -> error("Already resumed, but got $proposedUpdate") + + else -> error("Already resumed, but proposed with update $proposedUpdate") } } } - override fun handleException(exception: Throwable) { + private fun makeHandler(handler: CompletionHandler): CancellationHandlerImpl<*> { + if (handler is CancellationHandlerImpl<*>) { + require(handler.continuation === this) { "Handler has non-matching continuation ${handler.continuation}, current: $this" } + return handler + } + + return InvokeOnCancel(this, handler) + } + + private fun handleException(exception: Throwable) { handleCoroutineException(context, exception) } -} \ No newline at end of file + + private fun updateState(expect: NotCompleted, proposedUpdate: Any?, mode: Int): Boolean { + if (!tryUpdateState(expect, proposedUpdate)) { + return false + } + + completeUpdateState(expect, proposedUpdate, mode) + return true + } + + /** + * Completes update of the current [state] of this job. + * @suppress **This is unstable API and it is subject to change.** + */ + protected fun completeUpdateState(expect: NotCompleted, update: Any?, mode: Int) { + val exceptionally = update as? CompletedExceptionally + onCompletionInternal(mode) + + // Invoke cancellation handlers only if necessary + if (update is CancelledContinuation && expect is CancellationHandler) { + try { + expect.invoke(exceptionally?.cause) + } catch (ex: Throwable) { + handleException(CompletionHandlerException("Exception in completion handler $expect for $this", ex)) + } + } + } + + /** + * Tries to initiate update of the current [state] of this job. + * @suppress **This is unstable API and it is subject to change.** + */ + protected fun tryUpdateState(expect: NotCompleted, update: Any?): Boolean { + require(update !is NotCompleted) // only NotCompleted -> completed transition is allowed + if (!_state.compareAndSet(expect, update)) return false + // Unregister from parent job + parentHandle?.let { + it.dispose() // volatile read parentHandle _after_ state was updated + parentHandle = NonDisposableHandle // release it just in case, to aid GC + } + return true // continues in completeUpdateState + } + + // For nicer debugging + public override fun toString(): String = + "${nameString()}{${stateString()}}@$hexAddress" + + protected open fun nameString(): String = classSimpleName + + private fun stateString(): String { + val state = this.state + return when (state) { + is NotCompleted ->"Active" + is CancelledContinuation -> "Cancelled" + is CompletedExceptionally -> "CompletedExceptionally" + else -> "Completed" + } + } + +} + +// Marker for active continuation +internal interface NotCompleted + +private class Active : NotCompleted + +private val ACTIVE: Active = Active() + +internal abstract class CancellationHandlerImpl>(@JvmField val continuation: C) : + CancellationHandler(), NotCompleted + +// Wrapper for lambdas, for the performance sake CancellationHandler can be subclassed directly +private class InvokeOnCancel( // Clashes with InvokeOnCancellation + continuation: AbstractContinuation<*>, + private val handler: CompletionHandler +) : CancellationHandlerImpl>(continuation) { + + override fun invoke(cause: Throwable?) { + handler.invoke(cause) + } + + override fun toString() = "InvokeOnCancel[${handler.classSimpleName}@$hexAddress]" +} diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt index 3b4ff8a363..882b2cc926 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt @@ -57,24 +57,24 @@ import kotlin.coroutines.experimental.intrinsics.* * * ``` */ -public interface CancellableContinuation : Continuation, Job { +public interface CancellableContinuation : Continuation { /** * Returns `true` when this continuation is active -- it has not completed or cancelled yet. */ - public override val isActive: Boolean + public val isActive: Boolean /** * Returns `true` when this continuation has completed for any reason. A continuation * that was cancelled is also considered complete. */ - public override val isCompleted: Boolean + public val isCompleted: Boolean /** * Returns `true` if this continuation was [cancelled][cancel]. * * It implies that [isActive] is `false` and [isCompleted] is `true`. */ - public override val isCancelled: Boolean + public val isCancelled: Boolean /** * Tries to resume this continuation with a given value and returns non-null object token if it was successful, @@ -114,24 +114,37 @@ public interface CancellableContinuation : Continuation, Job { * Cancels this continuation with an optional cancellation [cause]. The result is `true` if this continuation was * cancelled as a result of this invocation and `false` otherwise. */ - @Suppress("DEFAULT_VALUE_NOT_ALLOWED_IN_OVERRIDE") - public override fun cancel(cause: Throwable? = null): Boolean + public fun cancel(cause: Throwable? = null): Boolean /** - * Registers handler that is **synchronously** invoked once on completion of this continuation. - * When continuation is already complete, then the handler is immediately invoked - * with continuation's exception or `null`. Otherwise, handler will be invoked once when this - * continuation is complete. + * Registers handler that is **synchronously** invoked once on cancellation (both regular and exceptional) of this continuation. + * When the continuation is already cancelled, then the handler is immediately invoked + * with cancellation exception. Otherwise, the handler will be invoked once on cancellation if this + * continuation is cancelled. * - * 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 continuation. The references to - * all the handlers are released when this continuation completes. + * Installed [handler] should not throw any exceptions. If it does, they will get caught, + * wrapped into [CompletionHandlerException], and rethrown, potentially causing the crash of unrelated code. + * + * At most one [handler] can be installed on one continuation + */ + @Deprecated( + message = "Disposable handlers on regular completion are no longer supported", + replaceWith = ReplaceWith("invokeOnCancellation(handler)"), + level = DeprecationLevel.WARNING) + public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle + + /** + * Registers handler that is **synchronously** invoked once on cancellation (both regular and exceptional) of this continuation. + * When the continuation is already cancelled, then the handler is immediately invoked + * with cancellation exception. Otherwise, the handler will be invoked once on cancellation if this + * continuation is cancelled. * * Installed [handler] should not throw any exceptions. If it does, they will get caught, - * wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code. + * wrapped into [CompletionHandlerException], and rethrown, potentially causing the crash of unrelated code. + * + * At most one [handler] can be installed on one continuation */ - public override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle + public fun invokeOnCancellation(handler: CompletionHandler) /** * Resumes this continuation with a given [value] in the invoker thread without going though @@ -193,44 +206,81 @@ public suspend inline fun suspendAtomicCancellableCoroutine( * Removes a given node on cancellation. * @suppress **This is unstable API and it is subject to change.** */ -public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNode): DisposableHandle = - invokeOnCompletion(handler = RemoveOnCancel(this, node).asHandler) +@Deprecated( + message = "Disposable handlers on cancellation are no longer supported", + replaceWith = ReplaceWith("removeOnCancellation(handler)"), + level = DeprecationLevel.WARNING) +public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNode): DisposableHandle { + invokeOnCancellation(handler = RemoveOnCancel(this as CancellableContinuationImpl<*>, node).asHandler) + return NonDisposableHandle +} + +/** + * Removes a given node on cancellation. + * @suppress **This is unstable API and it is subject to change.** + */ +public fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode): Unit = + invokeOnCancellation(handler = RemoveOnCancel(this as CancellableContinuationImpl<*>, node).asHandler) + +@Deprecated( + message = "Disposable handlers on regular completion are no longer supported", + replaceWith = ReplaceWith("disposeOnCancellation(handler)"), + level = DeprecationLevel.WARNING) +public fun CancellableContinuation<*>.disposeOnCompletion(handle: DisposableHandle): DisposableHandle { + invokeOnCancellation(handler = DisposeOnCancellation(this as CancellableContinuationImpl<*>, handle).asHandler) + return NonDisposableHandle +} + +public fun CancellableContinuation<*>.disposeOnCancellation(handle: DisposableHandle) = + invokeOnCancellation(handler = DisposeOnCancellation(this as CancellableContinuationImpl<*>, handle).asHandler) // --------------- implementation details --------------- +// TODO: With separate class IDEA fails private class RemoveOnCancel( - cont: CancellableContinuation<*>, + cont: CancellableContinuationImpl<*>, @JvmField val node: LockFreeLinkedListNode -) : JobNode>(cont) { +) : CancellationHandlerImpl>(cont) { + override fun invoke(cause: Throwable?) { - if (job.isCancelled) + if (continuation.isCancelled) node.remove() } + override fun toString() = "RemoveOnCancel[$node]" } +private class DisposeOnCancellation( + continuation: CancellableContinuationImpl<*>, + private val handle: DisposableHandle +) : CancellationHandlerImpl>(continuation) { + + override fun invoke(cause: Throwable?) = handle.dispose() + + override fun toString(): String = "DisposeOnCancellation[$handle]" +} + @PublishedApi internal class CancellableContinuationImpl( delegate: Continuation, resumeMode: Int ) : AbstractContinuation(delegate, resumeMode), CancellableContinuation, Runnable { - @Volatile // just in case -- we don't want an extra data race, even benign one - private var _context: CoroutineContext? = null // created on first need - public override val context: CoroutineContext - get() = _context ?: (delegate.context + this).also { _context = it } + public override val context: CoroutineContext = delegate.context override fun initCancellability() { initParentJobInternal(delegate.context[Job]) } - override val onCancelMode: Int get() = ON_CANCEL_MAKE_CANCELLED + override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle { + invokeOnCancellation(handler) + return NonDisposableHandle + } override fun tryResume(value: T, idempotent: Any?): Any? { - while (true) { // lock-free loop on state - val state = this.state // atomic read + loopOnState { state -> when (state) { - is Incomplete -> { + is NotCompleted -> { val update: Any? = if (idempotent == null) value else CompletedIdempotentResult(idempotent, value, state) if (tryUpdateState(state, update)) return state @@ -248,10 +298,9 @@ internal class CancellableContinuationImpl( } override fun tryResumeWithException(exception: Throwable): Any? { - while (true) { // lock-free loop on state - val state = this.state // atomic read - when (state) { - is Incomplete -> { + loopOnState { state -> + when (state) { + is NotCompleted -> { if (tryUpdateState(state, CompletedExceptionally(exception))) return state } else -> return null // cannot resume -- not active anymore @@ -260,7 +309,7 @@ internal class CancellableContinuationImpl( } override fun completeResume(token: Any) { - completeUpdateState(token as Incomplete, state, resumeMode) + completeUpdateState(token as NotCompleted, state, resumeMode) } override fun CoroutineDispatcher.resumeUndispatched(value: T) { @@ -277,19 +326,14 @@ internal class CancellableContinuationImpl( override fun getSuccessfulResult(state: Any?): T = if (state is CompletedIdempotentResult) state.result as T else state as T - override fun nameString(): String = + protected override fun nameString(): String = "CancellableContinuation(${delegate.toDebugString()})" - - // todo: This workaround for KT-21968, should be removed in the future - public override fun cancel(cause: Throwable?): Boolean = - super.cancel(cause) } private class CompletedIdempotentResult( @JvmField val idempotentResume: Any?, @JvmField val result: Any?, - @JvmField val token: Incomplete + @JvmField val token: NotCompleted ) { override fun toString(): String = "CompletedIdempotentResult[$result]" } - diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt index 8e3276e24d..bb4c4a0f29 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt @@ -17,6 +17,7 @@ package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.internalAnnotations.* +import kotlin.coroutines.experimental.* /** * Class for an internal state of a job that had completed exceptionally, including cancellation. @@ -61,7 +62,8 @@ public open class CompletedExceptionally protected constructor( * A specific subclass of [CompletedExceptionally] for cancelled jobs. * * **Note: This class cannot be used outside of internal coroutines framework**. - * + * TODO rename to CancelledJob? + * * @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. @@ -74,3 +76,19 @@ public class Cancelled( override fun createException(): Throwable = JobCancellationException("Job was cancelled normally", null, job) } +/** + * A specific subclass of [CompletedExceptionally] for cancelled [AbstractContinuation]. + * + * **Note: This class cannot be used outside of internal coroutines framework**. + * + * @param continuation the continuation that was cancelled. + * @param cause the exceptional completion cause. If `cause` is null, then a [JobCancellationException] + * if created on first get from [exception] property. + * @suppress **This is unstable API and it is subject to change.** + */ +public class CancelledContinuation( + private val continuation: Continuation<*>, + cause: Throwable? +) : CompletedExceptionally(cause, true) { + override fun createException(): Throwable = CancellationException("Coroutine $continuation was cancelled normally") +} diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt index dc2fd9ae31..c8669918ea 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt @@ -19,7 +19,7 @@ package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.internal.* /** - * Handler for [Job.invokeOnCompletion]. + * Handler for [Job.invokeOnCompletion] and [CancellableContinuation.invokeOnCancellation]. * * Installed handler should not throw any exceptions. If it does, they will get caught, * wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code. @@ -38,6 +38,12 @@ internal expect abstract class CompletionHandlerNode() : LockFreeLinkedListNode abstract fun invoke(cause: Throwable?) } +// More compact version of CompletionHandlerNode for CancellableContinuation with same workaround for JS +internal expect abstract class CancellationHandler() { + val asHandler: CompletionHandler + abstract fun invoke(cause: Throwable?) +} + // :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, // because we play type tricks on Kotlin/JS and handler is not necessarily a function there internal expect fun CompletionHandler.invokeIt(cause: Throwable?) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt index 7898059684..ce2659a1b6 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt @@ -402,7 +402,8 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 } private suspend fun joinSuspend() = suspendCancellableCoroutine { cont -> - cont.disposeOnCompletion(invokeOnCompletion(handler = ResumeOnCompletion(this, cont).asHandler)) + // We have to invoke join() handler only on cancellation, on completion we will be resumed regularly without handlers + cont.disposeOnCancellation(invokeOnCompletion(handler = ResumeOnCompletion(this, cont).asHandler)) } public final override val onJoin: SelectClause0 @@ -556,7 +557,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 return COMPLETING_ALREADY_COMPLETING if (state is Finishing && state.completing) return COMPLETING_ALREADY_COMPLETING - val child: Child? = firstChild(state) ?: // or else complete immediately w/o children + val child: ChildJob? = firstChild(state) ?: // or else complete immediately w/o children when { state !is Finishing && hasOnFinishingHandler(proposedUpdate) -> null // unless it has onFinishing handler updateState(state, proposedUpdate, mode) -> return COMPLETING_COMPLETED @@ -590,7 +591,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 } } - private tailrec fun Child.cancelChildrenInternal(cause: Throwable) { + private tailrec fun ChildJob.cancelChildrenInternal(cause: Throwable) { childJob.cancel(JobCancellationException("Child job was cancelled because of parent failure", cause, childJob)) nextChild()?.cancelChildrenInternal(cause) } @@ -599,10 +600,10 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 get() = (this as? CompletedExceptionally)?.exception private fun firstChild(state: Incomplete) = - state as? Child ?: state.list?.nextChild() + state as? ChildJob ?: state.list?.nextChild() // return false when there is no more incomplete children to wait - private tailrec fun tryWaitForChild(child: Child, proposedUpdate: Any?): Boolean { + private tailrec fun tryWaitForChild(child: ChildJob, proposedUpdate: Any?): Boolean { val handle = child.childJob.invokeOnCompletion(invokeImmediately = false, handler = ChildCompletion(this, child, proposedUpdate).asHandler) if (handle !== NonDisposableHandle) return true // child is not complete and we've started waiting for it @@ -613,7 +614,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 /** * @suppress **This is unstable API and it is subject to change.** */ - internal fun continueCompleting(lastChild: Child, proposedUpdate: Any?) { + internal fun continueCompleting(lastChild: ChildJob, proposedUpdate: Any?) { loopOnState { state -> if (state !is Finishing) throw IllegalStateException("Job $this is found in expected state while completing with $proposedUpdate", proposedUpdate.exceptionOrNull) @@ -626,13 +627,13 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 } } - private fun LockFreeLinkedListNode.nextChild(): Child? { + private fun LockFreeLinkedListNode.nextChild(): ChildJob? { var cur = this while (cur.isRemoved) cur = cur.prevNode // rollback to prev non-removed (or list head) while (true) { cur = cur.nextNode if (cur.isRemoved) continue - if (cur is Child) return cur + if (cur is ChildJob) return cur if (cur is NodeList) return null // checked all -- no more children } } @@ -640,16 +641,16 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 public final override val children: Sequence get() = buildSequence { val state = this@JobSupport.state when (state) { - is Child -> yield(state.childJob) + is ChildJob -> yield(state.childJob) is Incomplete -> state.list?.let { list -> - list.forEach { yield(it.childJob) } + list.forEach { yield(it.childJob) } } } } @Suppress("OverridingDeprecatedMember") public final override fun attachChild(child: Job): DisposableHandle = - invokeOnCompletion(onCancelling = true, handler = Child(this, child).asHandler) + invokeOnCompletion(onCancelling = true, handler = ChildJob(this, child).asHandler) @Suppress("OverridingDeprecatedMember") public final override fun cancelChildren(cause: Throwable?) { @@ -771,7 +772,8 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 } private suspend fun awaitSuspend(): Any? = suspendCancellableCoroutine { cont -> - cont.disposeOnCompletion(invokeOnCompletion { + // We have to invoke await() ha ndler only on cancellation, on completion we will be resumed regularly without handlers + cont.disposeOnCancellation(invokeOnCompletion { val state = this.state check(state !is Incomplete) if (state is CompletedExceptionally) @@ -962,7 +964,7 @@ private class InvokeOnCancellation( override fun toString() = "InvokeOnCancellation[$classSimpleName@$hexAddress]" } -internal class Child( +internal class ChildJob( parent: JobSupport, @JvmField val childJob: Job ) : JobCancellationNode(parent) { @@ -970,12 +972,23 @@ internal class Child( // Always materialize the actual instance of parent's completion exception and cancel child with it childJob.cancel(job.getCancellationException()) } - override fun toString(): String = "Child[$childJob]" + override fun toString(): String = "ChildJob[$childJob]" +} + +// Same as ChildJob, but for cancellable continuation +internal class ChildContinuation( + parent: Job, + @JvmField val child: AbstractContinuation<*> +) : JobCancellationNode(parent) { + override fun invoke(cause: Throwable?) { + child.cancel(job.getCancellationException()) + } + override fun toString(): String = "ChildContinuation[$child]" } private class ChildCompletion( private val parent: JobSupport, - private val child: Child, + private val child: ChildJob, private val proposedUpdate: Any? ) : JobNode(child.childJob) { override fun invoke(cause: Throwable?) { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CancellableContinuationHandlersTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CancellableContinuationHandlersTest.kt new file mode 100644 index 0000000000..eeade34b17 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CancellableContinuationHandlersTest.kt @@ -0,0 +1,90 @@ +package kotlinx.coroutines.experimental + +import kotlin.test.* + +class CancellableContinuationHandlersTest : TestBase() { + + @Test + fun testDoubleSubscription() = runTest({ it is IllegalStateException }) { + suspendCancellableCoroutine { c -> + c.invokeOnCancellation { finish(1) } + c.invokeOnCancellation { expectUnreached() } + } + } + + @Test + fun testDoubleSubscriptionAfterCompletion() = runTest { + suspendCancellableCoroutine { c -> + c.resume(Unit) + // Nothing happened + c.invokeOnCancellation { expectUnreached() } + c.invokeOnCancellation { expectUnreached() } + } + } + + @Test + fun testDoubleSubscriptionAfterCancellation() = runTest { + try { + suspendCancellableCoroutine { c -> + c.cancel() + c.invokeOnCancellation { + assertNull(it) + expect(1) + } + c.invokeOnCancellation { + assertNull(it) + expect(2) + } + } + } catch (e: CancellationException) { + finish(3) + } + } + + @Test + fun testDoubleSubscriptionAfterCancellationWithCause() = runTest { + try { + suspendCancellableCoroutine { c -> + c.cancel(AssertionError()) + c.invokeOnCancellation { + require(it is AssertionError) + expect(1) + } + c.invokeOnCancellation { + require(it is AssertionError) + expect(2) + } + } + } catch (e: AssertionError) { + finish(3) + } + } + + @Test + fun testDoubleSubscriptionMixed() = runTest { + try { + suspendCancellableCoroutine { c -> + c.invokeOnCancellation { + require(it is IndexOutOfBoundsException) + expect(1) + } + + c.cancel(IndexOutOfBoundsException()) + c.invokeOnCancellation { + require(it is IndexOutOfBoundsException) + expect(2) + } + } + } catch (e: IndexOutOfBoundsException) { + finish(3) + } + } + + @Test + fun testExceptionInHandler() = runTest({it is CancellationException}, listOf({e -> e is CompletionHandlerException})) { + suspendCancellableCoroutine { c -> + c.invokeOnCancellation { throw AssertionError() } + c.cancel() + } + } +} diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt index bcd0c3a1c4..f59aa6b5e1 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt @@ -125,6 +125,8 @@ class WithContextTest : TestBase() { val job = Job() job.cancel() // try to cancel before it has a chance to run withContext(job + wrapperDispatcher(coroutineContext), CoroutineStart.ATOMIC) { // but start atomically + // TODO here behaviour changed + require(!isActive) finish(2) yield() // but will cancel here expectUnreached() @@ -139,6 +141,7 @@ class WithContextTest : TestBase() { val job = Job() job.cancel() // try to cancel before it has a chance to run withContext(job + wrapperDispatcher(coroutineContext), CoroutineStart.UNDISPATCHED) { // but start atomically + require(isActive) finish(2) yield() // but will cancel here expectUnreached() @@ -153,6 +156,7 @@ class WithContextTest : TestBase() { try { expect(3) withContext(wrapperDispatcher(coroutineContext)) { + require(isActive) expect(5) job!!.cancel() // cancel itself throw TestException() // but throw a different exception @@ -184,7 +188,7 @@ class WithContextTest : TestBase() { job!!.cancel() // cancel itself } } catch (e: Throwable) { - expect(6) + expect(7) // make sure TestException, not CancellationException is thrown! assertTrue(e is JobCancellationException, "Caught $e") } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt index 0f5cd77fc8..f0a8d5253b 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt @@ -23,5 +23,10 @@ internal actual abstract class CompletionHandlerNode actual constructor() : Lock actual abstract override fun invoke(cause: Throwable?) } +internal actual abstract class CancellationHandler actual constructor() : CompletionHandler { + actual inline val asHandler: CompletionHandler get() = this + actual abstract override fun invoke(cause: Throwable?) +} + @Suppress("NOTHING_TO_INLINE") internal actual inline fun CompletionHandler.invokeIt(cause: Throwable?) = invoke(cause) \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt index b10f0cdcf7..6909b9929a 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt @@ -22,15 +22,46 @@ package kotlinx.coroutines.experimental import java.util.concurrent.* /** - * Cancels a specified [future] when this job is complete. - * + * Cancels a specified [future] when this job is cancelled. * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created). * ``` * invokeOnCompletion { future.cancel(false) } * ``` */ public fun Job.cancelFutureOnCompletion(future: Future<*>): DisposableHandle = - invokeOnCompletion(handler = CancelFutureOnCompletion(this, future)) + invokeOnCompletion(handler = CancelFutureOnCompletion(this, future)) // TODO make it work only on cancellation as well? + +/** + * Cancels a specified [future] when this job is cancelled. + * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created). + * ``` + * invokeOnCompletion { future.cancel(false) } + * ``` + */ +@Deprecated( + message = "Disposable handlers on regular completion are no longer supported", + replaceWith = ReplaceWith("cancelFutureOnCancellation(future)"), + level = DeprecationLevel.WARNING) +public fun CancellableContinuation<*>.cancelFutureOnCompletion(future: Future<*>): DisposableHandle { + cancelFutureOnCancellation(future) + return NonDisposableHandle +} + +/** + * Cancels a specified [future] when this job is cancelled. + * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created). + * ``` + * invokeOnCancellation { future.cancel(false) } + * ``` + */ +public fun CancellableContinuation<*>.cancelFutureOnCancellation(future: Future<*>) { + if (this is AbstractContinuation<*>) { + invokeOnCancellation(handler = CancelFutureOnCancellation(this, future)) + } else { + // Fallback if someone else implement CancellableContinuation + invokeOnCancellation { future.cancel(false) } + } +} private class CancelFutureOnCompletion( job: Job, @@ -44,3 +75,15 @@ private class CancelFutureOnCompletion( override fun toString() = "CancelFutureOnCompletion[$future]" } +private class CancelFutureOnCancellation( + continuation: CancellableContinuation<*>, + private val future: Future<*> +) : CancellationHandlerImpl>(continuation as AbstractContinuation<*>) { + + 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() = "CancelFutureOnCancellation[$future]" +} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt index 82db551554..ee3ce38b05 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt @@ -24,6 +24,12 @@ internal actual abstract class CompletionHandlerNode : LinkedListNode() { actual abstract fun invoke(cause: Throwable?) } +internal actual abstract class CancellationHandler { + @Suppress("UnsafeCastFromDynamic") + actual inline val asHandler: CompletionHandler get() = asDynamic() + actual abstract fun invoke(cause: Throwable?) +} + internal actual fun CompletionHandler.invokeIt(cause: Throwable?) { when(jsTypeOf(this)) { "function" -> invoke(cause) From 4aa18aa81a2a9ccee3d7f37ad094212b373b4f16 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 17 Apr 2018 15:43:12 +0300 Subject: [PATCH 28/61] Introduce cancelling state for AbstractContinuation, improve exception handling, make tests stricter --- .../experimental/AbstractContinuation.kt | 177 ++++++++++++------ .../experimental/Builders.common.kt | 5 +- .../experimental/WithContextTest.kt | 35 ++-- .../coroutines/experimental/JoinStressTest.kt | 3 +- .../WithContextCancellationStressTest.kt | 80 ++++++++ .../experimental/channels/ProduceTest.kt | 1 + 6 files changed, 232 insertions(+), 69 deletions(-) create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCancellationStressTest.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt index c76e304e82..c8e4c11d04 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt @@ -34,15 +34,23 @@ internal abstract class AbstractContinuation( ) : Continuation, DispatchedTask { /* + * Implementation notes + * * AbstractContinuation is a subset of Job with following limitations: * 1) It can have only cancellation listeners * 2) It always invokes cancellation listener if it's cancelled (no 'invokeImmediately') * 3) It can have at most one cancellation listener - * 4) It cannot be in cancelling state, only active/finished/cancelled - * 5) Its cancellation listeners cannot be deregistered - * + * 4) Its cancellation listeners cannot be deregistered * As a consequence it has much simpler state machine, more lightweight machinery and * less dependencies. + * + * Cancelling state + * If useCancellingState is true, then this continuation can have additional cancelling state, + * which is transition from Active to Cancelled. This is specific state to support withContext(ctx) + * construction: block in withContext can be cancelled from withing or even before stepping into withContext, + * but we still want to properly run it (e.g. when it has atomic cancellation mode) and run its completion listener + * after. + * During cancellation all pending exceptions are aggregated and thrown during transition to final state */ /* decision state machine @@ -67,6 +75,7 @@ internal abstract class AbstractContinuation( ------ ------------ ------------ ----------- ACTIVE Active : Active active, no listeners SINGLE_A CancellationHandler : Active active, one cancellation listener + CANCELLING Cancelling : Active in the process of cancellation due to cancellation of parent job CANCELLED Cancelled : Cancelled cancelled (final state) COMPLETED any : Completed produced some result or threw an exception (final state) */ @@ -83,6 +92,8 @@ internal abstract class AbstractContinuation( public val isCancelled: Boolean get() = state is CancelledContinuation + protected open val useCancellingState: Boolean get() = false + internal fun initParentJobInternal(parent: Job?) { check(parentHandle == null) if (parent == null) { @@ -105,6 +116,7 @@ internal abstract class AbstractContinuation( public fun cancel(cause: Throwable?): Boolean { loopOnState { state -> if (state !is NotCompleted) return false // quit if already complete + if (state is Cancelling) return false // someone else succeeded if (updateStateCancelled(state, cause)) return true } } @@ -174,8 +186,15 @@ internal abstract class AbstractContinuation( } } - private fun updateStateCancelled(state: NotCompleted, cause: Throwable?): Boolean = - updateState(state, CancelledContinuation(this, cause), mode = MODE_ATOMIC_DEFAULT) + private fun updateStateCancelled(state: NotCompleted, cause: Throwable?): Boolean { + val update: Any = if (useCancellingState) { + Cancelling(CancelledContinuation(this, cause)) + } else { + CancelledContinuation(this, cause) + } + + return updateState(state, update, mode = MODE_ATOMIC_DEFAULT) + } private fun onCompletionInternal(mode: Int) { if (tryResume()) return // completed before getResult invocation -- bail out @@ -192,50 +211,102 @@ internal abstract class AbstractContinuation( protected fun resumeImpl(proposedUpdate: Any?, resumeMode: Int) { loopOnState { state -> when (state) { - is NotCompleted -> { - if (updateState(state, proposedUpdate, resumeMode)) return - } - - is CancelledContinuation -> { - if (proposedUpdate !is CompletedExceptionally) { - return // Cancelled continuation completed, do nothing - } - + is Cancelling -> { // withContext() support /* - * Coerce concurrent cancellation and pending thrown exception. - * E.g. for linear history `T1: cancel() T2 (job): throw e T3: job.await()` - * we'd like to see actual exception in T3, not JobCancellationException. - * So thrown exception overwrites cancellation exception, but - * suppresses its non-null cause. + * If already cancelled block is resumed with non-exception, + * resume it with cancellation exception. + * E.g. + * ``` + * val value = withContext(ctx) { + * outerJob.cancel() // -> cancelling + * 42 // -> cancelled + * } + * ``` + * should throw cancellation exception instead of returning 42 */ - if (state.exception is CancellationException && state.exception.cause == null) { - return // Do not add to suppressed regular cancellation - } - - if (state.exception is CancellationException && state.exception.cause === proposedUpdate.exception) { - return // Do not add to suppressed cancellation with the same cause - } - - if (state.exception === proposedUpdate.exception) { - return // Do no add to suppressed the same exception - } - - val exception = proposedUpdate.exception - val update = CompletedExceptionally(exception) - if (state.cause != null) { - exception.addSuppressedThrowable(state.cause) + if (proposedUpdate !is CompletedExceptionally) { + val update = state.cancel + if (updateState(state, update, resumeMode)) return + } else { + /* + * If already cancelled block is resumed with an exception, + * then we should properly merge them to avoid information loss + */ + val update: CompletedExceptionally + + /* + * Proposed update is another CancellationException. + * e.g. + * ``` + * T1: ctxJob.cancel(e1) // -> cancelling + * T2: + * withContext(ctx) { + * // -> resumed with cancellation exception + * } + * ``` + */ + if (proposedUpdate.exception is CancellationException) { + // Keep original cancellation cause and try add to suppressed exception from proposed cancel + update = state.cancel + coerceWithCancellation(state, proposedUpdate, update) + } else { + /* + * Proposed update is exception => transition to terminal state + * E.g. + * ``` + * withContext(ctx) { + * outerJob.cancel() // -> cancelling + * throw Exception() // -> completed exceptionally + * } + * ``` + */ + val exception = proposedUpdate.exception + // TODO clashes with await all + val currentException = state.cancel.cause + // Add to suppressed if original cancellation differs from proposed exception + if (currentException != null && (currentException !is CancellationException || currentException.cause !== exception)) { + exception.addSuppressedThrowable(currentException) + } + + update = CompletedExceptionally(exception) + } + + if (updateState(state, update, resumeMode)) { + return + } } + } - if (_state.compareAndSet(state, update)) { - return + is NotCompleted -> { + if (updateState(state, proposedUpdate, resumeMode)) return + } + is CancelledContinuation -> { + if (proposedUpdate is NotCompleted || proposedUpdate is CompletedExceptionally) { + error("Unexpected update, state: $state, update: $proposedUpdate") } + // Coroutine is dispatched normally (e.g.via `delay()`) after cancellation + return } - else -> error("Already resumed, but proposed with update $proposedUpdate") } } } + // Coerce current cancelling state with proposed cancellation + private fun coerceWithCancellation(state: Cancelling, proposedUpdate: CompletedExceptionally, update: CompletedExceptionally) { + val originalCancellation = state.cancel + val originalException = originalCancellation.exception + val updateCause = proposedUpdate.cause + + // Cause of proposed update is present and differs from one in current state TODO clashes with await all + val isSameCancellation = originalCancellation.exception is CancellationException + && originalException.cause === updateCause?.cause + + if (!isSameCancellation && updateCause !== null && originalException.cause !== updateCause) { + update.exception.addSuppressedThrowable(updateCause) + } + } + private fun makeHandler(handler: CompletionHandler): CancellationHandlerImpl<*> { if (handler is CancellationHandlerImpl<*>) { require(handler.continuation === this) { "Handler has non-matching continuation ${handler.continuation}, current: $this" } @@ -250,18 +321,17 @@ internal abstract class AbstractContinuation( } private fun updateState(expect: NotCompleted, proposedUpdate: Any?, mode: Int): Boolean { + // TODO slow path for cancelling? if (!tryUpdateState(expect, proposedUpdate)) { return false } - completeUpdateState(expect, proposedUpdate, mode) + if (proposedUpdate !is Cancelling) { + completeUpdateState(expect, proposedUpdate, mode) + } return true } - /** - * Completes update of the current [state] of this job. - * @suppress **This is unstable API and it is subject to change.** - */ protected fun completeUpdateState(expect: NotCompleted, update: Any?, mode: Int) { val exceptionally = update as? CompletedExceptionally onCompletionInternal(mode) @@ -276,17 +346,16 @@ internal abstract class AbstractContinuation( } } - /** - * Tries to initiate update of the current [state] of this job. - * @suppress **This is unstable API and it is subject to change.** - */ protected fun tryUpdateState(expect: NotCompleted, update: Any?): Boolean { - require(update !is NotCompleted) // only NotCompleted -> completed transition is allowed if (!_state.compareAndSet(expect, update)) return false - // Unregister from parent job - parentHandle?.let { - it.dispose() // volatile read parentHandle _after_ state was updated - parentHandle = NonDisposableHandle // release it just in case, to aid GC + + // TODO separate code path? + if (update !is Cancelling) { + // Unregister from parent job + parentHandle?.let { + it.dispose() // volatile read parentHandle _after_ state was updated + parentHandle = NonDisposableHandle // release it just in case, to aid GC + } } return true // continues in completeUpdateState } @@ -313,9 +382,11 @@ internal abstract class AbstractContinuation( internal interface NotCompleted private class Active : NotCompleted - private val ACTIVE: Active = Active() +// In progress of cancellation +internal class Cancelling(@JvmField val cancel: CancelledContinuation) : NotCompleted + internal abstract class CancellationHandlerImpl>(@JvmField val continuation: C) : CancellationHandler(), NotCompleted diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt index 5c6d1f20c1..55f8cadc62 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt @@ -194,4 +194,7 @@ private class RunCompletion( override val context: CoroutineContext, delegate: Continuation, resumeMode: Int -) : AbstractContinuation(delegate, resumeMode) +) : AbstractContinuation(delegate, resumeMode) { + + override val useCancellingState: Boolean get() = true +} diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt index f59aa6b5e1..bb5af793dc 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithContextTest.kt @@ -118,18 +118,22 @@ class WithContextTest : TestBase() { } @Test - fun testRunAtomicTryCancel() = runTest( - expected = { it is JobCancellationException } - ) { + fun testRunAtomicTryCancel() = runTest { expect(1) val job = Job() job.cancel() // try to cancel before it has a chance to run - withContext(job + wrapperDispatcher(coroutineContext), CoroutineStart.ATOMIC) { // but start atomically - // TODO here behaviour changed - require(!isActive) - finish(2) - yield() // but will cancel here - expectUnreached() + + try { + withContext(job + wrapperDispatcher(coroutineContext), CoroutineStart.ATOMIC) { + require(isActive) + // but start atomically + expect(2) + yield() // but will cancel here + expectUnreached() + } + } catch (e: JobCancellationException) { + // This block should be invoked *after* context body + finish(3) } } @@ -158,12 +162,14 @@ class WithContextTest : TestBase() { withContext(wrapperDispatcher(coroutineContext)) { require(isActive) expect(5) - job!!.cancel() // cancel itself + require(job!!.cancel()) // cancel itself + require(!job!!.cancel(AssertionError())) // cancel again, no success here + require(!isActive) throw TestException() // but throw a different exception } } catch (e: Throwable) { expect(7) - // make sure TestException, not CancellationException is thrown! + // make sure TestException, not CancellationException or AssertionError is thrown assertTrue(e is TestException, "Caught $e") } } @@ -184,12 +190,15 @@ class WithContextTest : TestBase() { try { expect(3) withContext(wrapperDispatcher(coroutineContext)) { + require(isActive) expect(5) - job!!.cancel() // cancel itself + require(job!!.cancel()) // cancel itself + require(!job!!.cancel(AssertionError())) // cancel again, no success here + require(!isActive) } } catch (e: Throwable) { expect(7) - // make sure TestException, not CancellationException is thrown! + // make sure TestException, not CancellationException or AssertionError is thrown! assertTrue(e is JobCancellationException, "Caught $e") } } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JoinStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JoinStressTest.kt index d58f27f252..665d0cebcb 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JoinStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/JoinStressTest.kt @@ -8,8 +8,7 @@ import kotlin.test.* class JoinStressTest : TestBase() { - val iterations = 50_000 * stressTestMultiplier - + private val iterations = 50_000 * stressTestMultiplier private val pool = newFixedThreadPoolContext(3, "JoinStressTest") @After diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCancellationStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCancellationStressTest.kt new file mode 100644 index 0000000000..e028fbf8cf --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCancellationStressTest.kt @@ -0,0 +1,80 @@ +package kotlinx.coroutines.experimental + +import org.junit.* +import java.io.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* + +class WithContextCancellationStressTest : TestBase() { + + private val iterations = 150_000 * stressTestMultiplier + private val pool = newFixedThreadPoolContext(3, "WithContextCancellationStressTest") + + @After + fun tearDown() { + pool.close() + } + + @Test + fun testConcurrentCancellation() = runBlocking { + var ioException = 0 + var arithmeticException = 0 + var aiobException = 0 + + repeat(iterations) { + val barrier = CyclicBarrier(4) + val jobWithContext = async(pool) { + barrier.await() + withContext(wrapperDispatcher(coroutineContext)) { + throw IOException() + } + } + + val cancellerJob = async(pool) { + barrier.await() + jobWithContext.cancel(ArithmeticException()) + } + + val cancellerJob2 = async(pool) { + barrier.await() + jobWithContext.cancel(ArrayIndexOutOfBoundsException()) + } + + barrier.await() + val c1 = cancellerJob.await() + val c2 = cancellerJob2.await() + require(!(c1 && c2)) { "Same job cannot be cancelled twice" } + + try { + jobWithContext.await() + } catch (e: Exception) { + when (e) { + is IOException -> ++ioException + is JobCancellationException -> { + val cause = e.cause + when (cause) { + is ArithmeticException -> ++arithmeticException + is ArrayIndexOutOfBoundsException -> ++aiobException + else -> error("Unexpected exception") + } + } + else -> error("Unexpected exception") + } + } + } + + // Backward compatibility, no exceptional code paths were lost + require(ioException > 0) { "At least one IOException expected" } + require(arithmeticException > 0) { "At least one ArithmeticException expected" } + require(aiobException > 0) { "At least one ArrayIndexOutOfBoundsException expected" } + } + + private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext { + val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher + return object : CoroutineDispatcher() { + override fun dispatch(context: CoroutineContext, block: Runnable) { + dispatcher.dispatch(context, block) + } + } + } +} 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 index 641eff0f62..6fa7f76e26 100644 --- 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 @@ -18,6 +18,7 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* import org.junit.* +import java.io.* import kotlin.coroutines.experimental.* class ProduceTest : TestBase() { From 80a29477d8d722b9f4e6af6bd82c289451615096 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 17 Apr 2018 16:02:02 +0300 Subject: [PATCH 29/61] Make deprecated API hidden, replace deprecated API with new one --- .../experimental/CancellableContinuation.kt | 22 ++++++++++++++++--- .../coroutines/experimental/sync/Mutex.kt | 2 +- .../coroutines/experimental/Executors.kt | 2 +- .../kotlinx/coroutines/experimental/Future.kt | 2 +- .../experimental/channels/AbstractChannel.kt | 6 ++--- .../experimental/guava/ListenableFuture.kt | 2 +- .../coroutines/experimental/future/Future.kt | 2 +- .../coroutines/experimental/nio/Nio.kt | 3 +-- .../coroutines/experimental/quasar/Quasar.kt | 6 ++--- .../coroutines/experimental/reactive/Await.kt | 2 +- .../experimental/reactor/Scheduler.kt | 2 +- .../coroutines/experimental/rx1/RxAwait.kt | 2 +- .../coroutines/experimental/rx2/RxAwait.kt | 4 ++-- .../coroutines/experimental/javafx/JavaFx.kt | 2 +- .../coroutines/experimental/swing/Swing.kt | 2 +- 15 files changed, 37 insertions(+), 24 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt index 882b2cc926..718a6016ad 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt @@ -130,7 +130,7 @@ public interface CancellableContinuation : Continuation { @Deprecated( message = "Disposable handlers on regular completion are no longer supported", replaceWith = ReplaceWith("invokeOnCancellation(handler)"), - level = DeprecationLevel.WARNING) + level = DeprecationLevel.HIDDEN) public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle /** @@ -209,7 +209,7 @@ public suspend inline fun suspendAtomicCancellableCoroutine( @Deprecated( message = "Disposable handlers on cancellation are no longer supported", replaceWith = ReplaceWith("removeOnCancellation(handler)"), - level = DeprecationLevel.WARNING) + level = DeprecationLevel.HIDDEN) public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNode): DisposableHandle { invokeOnCancellation(handler = RemoveOnCancel(this as CancellableContinuationImpl<*>, node).asHandler) return NonDisposableHandle @@ -222,15 +222,31 @@ public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNod public fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode): Unit = invokeOnCancellation(handler = RemoveOnCancel(this as CancellableContinuationImpl<*>, node).asHandler) +/** + * Disposes a specified [handle] when this continuation is cancelled. + * + * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created). + * ``` + * invokeOnCancellation { handle.dispose() } + * ``` + */ @Deprecated( message = "Disposable handlers on regular completion are no longer supported", replaceWith = ReplaceWith("disposeOnCancellation(handler)"), - level = DeprecationLevel.WARNING) + level = DeprecationLevel.HIDDEN) public fun CancellableContinuation<*>.disposeOnCompletion(handle: DisposableHandle): DisposableHandle { invokeOnCancellation(handler = DisposeOnCancellation(this as CancellableContinuationImpl<*>, handle).asHandler) return NonDisposableHandle } +/** + * Disposes a specified [handle] when this continuation is cancelled. + * + * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created). + * ``` + * invokeOnCancellation { handle.dispose() } + * ``` + */ public fun CancellableContinuation<*>.disposeOnCancellation(handle: DisposableHandle) = invokeOnCancellation(handler = DisposeOnCancellation(this as CancellableContinuationImpl<*>, handle).asHandler) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt index 98070597fb..7c650c5326 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt @@ -230,7 +230,7 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 { if (state.addLastIf(waiter, { _state.value === state })) { // added to waiter list! cont.initCancellability() // make it properly cancellable - cont.removeOnCancel(waiter) + cont.removeOnCancellation(waiter) return@sc } } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt index f20753a7bc..67f3b34701 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Executors.kt @@ -72,7 +72,7 @@ public abstract class ExecutorCoroutineDispatcherBase : CloseableCoroutineDispat ?.schedule(ResumeUndispatchedRunnable(this, continuation), time, unit) } catch (e: RejectedExecutionException) { null } if (timeout != null) - continuation.cancelFutureOnCompletion(timeout) + continuation.cancelFutureOnCancellation(timeout) else DefaultExecutor.scheduleResumeAfterDelay(time, unit, continuation) } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt index 6909b9929a..3490d14ae9 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt @@ -41,7 +41,7 @@ public fun Job.cancelFutureOnCompletion(future: Future<*>): DisposableHandle = @Deprecated( message = "Disposable handlers on regular completion are no longer supported", replaceWith = ReplaceWith("cancelFutureOnCancellation(future)"), - level = DeprecationLevel.WARNING) + level = DeprecationLevel.HIDDEN) public fun CancellableContinuation<*>.cancelFutureOnCompletion(future: Future<*>): DisposableHandle { cancelFutureOnCancellation(future) return NonDisposableHandle diff --git a/core/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 index 4ce28dad7b..5db28ba13c 100644 --- a/core/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 @@ -193,7 +193,7 @@ public abstract class AbstractSendChannel : SendChannel { when (enqueueResult) { null -> { // enqueued successfully cont.initCancellability() // make it properly cancellable - cont.removeOnCancel(send) + cont.removeOnCancellation(send) return@sc } is Closed<*> -> { @@ -769,8 +769,8 @@ public abstract class AbstractChannel : AbstractSendChannel(), Channel // ------ private ------ private fun removeReceiveOnCancel(cont: CancellableContinuation<*>, receive: Receive<*>) { - cont.invokeOnCompletion { - if (cont.isCancelled && receive.remove()) + cont.invokeOnCancellation { + if (receive.remove()) onReceiveDequeued() } } 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 81bbbb5d70..cec625b2b1 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 @@ -129,7 +129,7 @@ private class DeferredListenableFuture( public suspend fun ListenableFuture.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation -> val callback = ContinuationCallback(cont) Futures.addCallback(this, callback) - cont.invokeOnCompletion { + cont.invokeOnCancellation { callback.cont = null // clear the reference to continuation from the future's callback } } 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 ee908e3fc1..89846bd9a8 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 @@ -176,7 +176,7 @@ public suspend fun CompletionStage.await(): T { return suspendCancellableCoroutine { cont: CancellableContinuation -> val consumer = ContinuationConsumer(cont) whenComplete(consumer) - cont.invokeOnCompletion { + cont.invokeOnCancellation { consumer.cont = null // shall clear reference to continuation } } diff --git a/integration/kotlinx-coroutines-nio/src/main/kotlin/kotlinx/coroutines/experimental/nio/Nio.kt b/integration/kotlinx-coroutines-nio/src/main/kotlin/kotlinx/coroutines/experimental/nio/Nio.kt index 7fabfebe32..fdcdee36b0 100644 --- a/integration/kotlinx-coroutines-nio/src/main/kotlin/kotlinx/coroutines/experimental/nio/Nio.kt +++ b/integration/kotlinx-coroutines-nio/src/main/kotlin/kotlinx/coroutines/experimental/nio/Nio.kt @@ -136,8 +136,7 @@ suspend fun AsynchronousSocketChannel.aWrite( // ---------------- private details ---------------- private fun Channel.closeOnCancel(cont: CancellableContinuation<*>) { - cont.invokeOnCompletion { - if (cont.isCancelled) + cont.invokeOnCancellation { try { close() } catch (ex: Throwable) { diff --git a/integration/kotlinx-coroutines-quasar/src/main/kotlin/kotlinx/coroutines/experimental/quasar/Quasar.kt b/integration/kotlinx-coroutines-quasar/src/main/kotlin/kotlinx/coroutines/experimental/quasar/Quasar.kt index a88ef8e2d7..ff53e06d98 100644 --- a/integration/kotlinx-coroutines-quasar/src/main/kotlin/kotlinx/coroutines/experimental/quasar/Quasar.kt +++ b/integration/kotlinx-coroutines-quasar/src/main/kotlin/kotlinx/coroutines/experimental/quasar/Quasar.kt @@ -37,9 +37,7 @@ import co.paralleluniverse.fibers.FiberAsync import co.paralleluniverse.fibers.SuspendExecution import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.SuspendableCallable -import kotlinx.coroutines.experimental.asCoroutineDispatcher -import kotlinx.coroutines.experimental.cancelFutureOnCompletion -import kotlinx.coroutines.experimental.suspendCancellableCoroutine +import kotlinx.coroutines.experimental.* import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.CoroutineContext import kotlin.coroutines.experimental.startCoroutine @@ -59,7 +57,7 @@ suspend fun runSuspendable(callable: SuspendableCallable): T = suspendCan cont.resume(result) } } - cont.cancelFutureOnCompletion(fiber) + cont.cancelFutureOnCancellation(fiber) fiber.start() } diff --git a/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Await.kt b/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Await.kt index 62a9cc6e7d..f283ba0038 100644 --- a/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Await.kt +++ b/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Await.kt @@ -111,7 +111,7 @@ private suspend fun Publisher.awaitOne( override fun onSubscribe(sub: Subscription) { subscription = sub - cont.invokeOnCompletion { sub.cancel() } + cont.invokeOnCancellation { sub.cancel() } sub.request(if (mode == Mode.FIRST) 1 else Long.MAX_VALUE) } diff --git a/reactive/kotlinx-coroutines-reactor/src/main/kotlin/kotlinx/coroutines/experimental/reactor/Scheduler.kt b/reactive/kotlinx-coroutines-reactor/src/main/kotlin/kotlinx/coroutines/experimental/reactor/Scheduler.kt index f0d3d1084c..857df2cb47 100644 --- a/reactive/kotlinx-coroutines-reactor/src/main/kotlin/kotlinx/coroutines/experimental/reactor/Scheduler.kt +++ b/reactive/kotlinx-coroutines-reactor/src/main/kotlin/kotlinx/coroutines/experimental/reactor/Scheduler.kt @@ -24,7 +24,7 @@ open class SchedulerCoroutineDispatcher(private val scheduler: Scheduler) : Coro val disposable = scheduler.schedule({ with(continuation) { resumeUndispatched(Unit) } }, time, unit) - continuation.disposeOnCompletion(disposable.asDisposableHandle()) + continuation.disposeOnCancellation(disposable.asDisposableHandle()) } override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle = diff --git a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxAwait.kt b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxAwait.kt index b8e68a2411..0a567a7350 100644 --- a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxAwait.kt +++ b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxAwait.kt @@ -137,5 +137,5 @@ private suspend fun Observable.awaitOne(): T = suspendCancellableCoroutin } internal fun CancellableContinuation.unsubscribeOnCompletion(sub: Subscription) { - invokeOnCompletion { sub.unsubscribe() } + invokeOnCancellation { sub.unsubscribe() } } diff --git a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxAwait.kt b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxAwait.kt index c8f8df9105..d0a5167563 100644 --- a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxAwait.kt +++ b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxAwait.kt @@ -162,7 +162,7 @@ public suspend fun ObservableSource.awaitSingle(): T = awaitOne(Mode.SING // ------------------------ private ------------------------ internal fun CancellableContinuation<*>.disposeOnCompletion(d: Disposable) = - invokeOnCompletion { d.dispose() } + invokeOnCancellation { d.dispose() } private enum class Mode(val s: String) { FIRST("awaitFirst"), @@ -183,7 +183,7 @@ private suspend fun ObservableSource.awaitOne( override fun onSubscribe(sub: Disposable) { subscription = sub - cont.invokeOnCompletion { sub.dispose() } + cont.invokeOnCancellation { sub.dispose() } } override fun onNext(t: T) { 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 21b35c8771..3cec28be40 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 @@ -58,7 +58,7 @@ object JavaFx : CoroutineDispatcher(), Delay { val timeline = schedule(time, unit, EventHandler { with(continuation) { resumeUndispatched(Unit) } }) - continuation.invokeOnCompletion { timeline.stop() } + continuation.invokeOnCancellation { timeline.stop() } } override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { diff --git a/ui/kotlinx-coroutines-swing/src/main/kotlin/kotlinx/coroutines/experimental/swing/Swing.kt b/ui/kotlinx-coroutines-swing/src/main/kotlin/kotlinx/coroutines/experimental/swing/Swing.kt index 44695c6f57..a901c586fd 100644 --- a/ui/kotlinx-coroutines-swing/src/main/kotlin/kotlinx/coroutines/experimental/swing/Swing.kt +++ b/ui/kotlinx-coroutines-swing/src/main/kotlin/kotlinx/coroutines/experimental/swing/Swing.kt @@ -37,7 +37,7 @@ object Swing : CoroutineDispatcher(), Delay { val timer = schedule(time, unit, ActionListener { with(continuation) { resumeUndispatched(Unit) } }) - continuation.invokeOnCompletion { timer.stop() } + continuation.invokeOnCancellation { timer.stop() } } override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { From f6430f480f68f70f19238c008f641ab510740fd8 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 17 Apr 2018 17:56:32 +0300 Subject: [PATCH 30/61] Cleanup: Implementation-independent extensions Remove and encapsulate parts of JobSupport Assert invariants in AbstractContinuation Use separate code path for Cancelling state --- .../experimental/AbstractContinuation.kt | 85 +++++++++---------- .../experimental/CancellableContinuation.kt | 42 ++++++--- .../coroutines/experimental/JobSupport.kt | 26 ++---- .../kotlinx/coroutines/experimental/Future.kt | 4 +- .../WithContextCancellationStressTest.kt | 8 +- 5 files changed, 82 insertions(+), 83 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt index c8e4c11d04..94db61f4da 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt @@ -104,7 +104,7 @@ internal abstract class AbstractContinuation( val handle = parent.invokeOnCompletion(onCancelling = true, handler = ChildContinuation(parent, this).asHandler) parentHandle = handle - // now check our state _after_ registering (see updateState order of actions) + // now check our state _after_ registering (see updateStateToFinal order of actions) if (isCompleted) { handle.dispose() parentHandle = NonDisposableHandle // release it just in case, to aid GC @@ -117,7 +117,7 @@ internal abstract class AbstractContinuation( loopOnState { state -> if (state !is NotCompleted) return false // quit if already complete if (state is Cancelling) return false // someone else succeeded - if (updateStateCancelled(state, cause)) return true + if (tryCancel(state, cause)) return true } } @@ -170,8 +170,8 @@ internal abstract class AbstractContinuation( is CancelledContinuation -> { /* * Continuation is complete, invoke directly. - * NOTE: multiple invokeOnCancellation calls with different handlers are allowed on completed coroutine. - * It's slightly inconsistent with running coroutine, but currently, we have no mechanism to check + * NOTE: multiple invokeOnCancellation calls with different handlers are allowed on cancelled continuation. + * It's inconsistent with running continuation, but currently, we have no mechanism to check * whether any handler was registered during continuation lifecycle without additional overhead. * This may be changed in the future. * @@ -181,19 +181,28 @@ internal abstract class AbstractContinuation( handler.invokeIt((state as? CompletedExceptionally)?.cause) return } + is Cancelling -> error("Cancellation handlers for continuations with 'Cancelling' state are not supported") else -> return } } } - private fun updateStateCancelled(state: NotCompleted, cause: Throwable?): Boolean { - val update: Any = if (useCancellingState) { - Cancelling(CancelledContinuation(this, cause)) - } else { - CancelledContinuation(this, cause) + private fun makeHandler(handler: CompletionHandler): CancellationHandlerImpl<*> { + if (handler is CancellationHandlerImpl<*>) { + require(handler.continuation === this) { "Handler has non-matching continuation ${handler.continuation}, current: $this" } + return handler + } + + return InvokeOnCancel(this, handler) + } + + private fun tryCancel(state: NotCompleted, cause: Throwable?): Boolean { + if (useCancellingState) { + require(state !is CancellationHandler) { "Invariant: 'Cancelling' state and cancellation handlers cannot be used together" } + return _state.compareAndSet(state, Cancelling(CancelledContinuation(this, cause))) } - return updateState(state, update, mode = MODE_ATOMIC_DEFAULT) + return updateStateToFinal(state, CancelledContinuation(this, cause), mode = MODE_ATOMIC_DEFAULT) } private fun onCompletionInternal(mode: Int) { @@ -226,7 +235,7 @@ internal abstract class AbstractContinuation( */ if (proposedUpdate !is CompletedExceptionally) { val update = state.cancel - if (updateState(state, update, resumeMode)) return + if (updateStateToFinal(state, update, resumeMode)) return } else { /* * If already cancelled block is resumed with an exception, @@ -271,14 +280,14 @@ internal abstract class AbstractContinuation( update = CompletedExceptionally(exception) } - if (updateState(state, update, resumeMode)) { + if (updateStateToFinal(state, update, resumeMode)) { return } } } is NotCompleted -> { - if (updateState(state, proposedUpdate, resumeMode)) return + if (updateStateToFinal(state, proposedUpdate, resumeMode)) return } is CancelledContinuation -> { if (proposedUpdate is NotCompleted || proposedUpdate is CompletedExceptionally) { @@ -307,32 +316,30 @@ internal abstract class AbstractContinuation( } } - private fun makeHandler(handler: CompletionHandler): CancellationHandlerImpl<*> { - if (handler is CancellationHandlerImpl<*>) { - require(handler.continuation === this) { "Handler has non-matching continuation ${handler.continuation}, current: $this" } - return handler + /** + * Tries to make transition from active to cancelled or completed state and invokes cancellation handler if necessary + */ + private fun updateStateToFinal(expect: NotCompleted, proposedUpdate: Any?, mode: Int): Boolean { + if (!tryUpdateStateToFinal(expect, proposedUpdate)) { + return false } - return InvokeOnCancel(this, handler) - } - - private fun handleException(exception: Throwable) { - handleCoroutineException(context, exception) + completeStateUpdate(expect, proposedUpdate, mode) + return true } - private fun updateState(expect: NotCompleted, proposedUpdate: Any?, mode: Int): Boolean { - // TODO slow path for cancelling? - if (!tryUpdateState(expect, proposedUpdate)) { - return false - } - - if (proposedUpdate !is Cancelling) { - completeUpdateState(expect, proposedUpdate, mode) + protected fun tryUpdateStateToFinal(expect: NotCompleted, update: Any?): Boolean { + require(update !is NotCompleted) // only NotCompleted -> completed transition is allowed + if (!_state.compareAndSet(expect, update)) return false + // Unregister from parent job + parentHandle?.let { + it.dispose() // volatile read parentHandle _after_ state was updated + parentHandle = NonDisposableHandle // release it just in case, to aid GC } - return true + return true // continues in completeStateUpdate } - protected fun completeUpdateState(expect: NotCompleted, update: Any?, mode: Int) { + protected fun completeStateUpdate(expect: NotCompleted, update: Any?, mode: Int) { val exceptionally = update as? CompletedExceptionally onCompletionInternal(mode) @@ -346,18 +353,8 @@ internal abstract class AbstractContinuation( } } - protected fun tryUpdateState(expect: NotCompleted, update: Any?): Boolean { - if (!_state.compareAndSet(expect, update)) return false - - // TODO separate code path? - if (update !is Cancelling) { - // Unregister from parent job - parentHandle?.let { - it.dispose() // volatile read parentHandle _after_ state was updated - parentHandle = NonDisposableHandle // release it just in case, to aid GC - } - } - return true // continues in completeUpdateState + private fun handleException(exception: Throwable) { + handleCoroutineException(context, exception) } // For nicer debugging diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt index 718a6016ad..6e4356dd20 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt @@ -24,7 +24,7 @@ import kotlin.coroutines.experimental.intrinsics.* // --------------- cancellable continuations --------------- /** - * Cancellable continuation. Its job is _completed_ when it is resumed or cancelled. + * Cancellable continuation. It is _completed_ when it is resumed or cancelled. * When [cancel] function is explicitly invoked, this continuation immediately resumes with [CancellationException] or * with the specified cancel cause. * @@ -211,7 +211,7 @@ public suspend inline fun suspendAtomicCancellableCoroutine( replaceWith = ReplaceWith("removeOnCancellation(handler)"), level = DeprecationLevel.HIDDEN) public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNode): DisposableHandle { - invokeOnCancellation(handler = RemoveOnCancel(this as CancellableContinuationImpl<*>, node).asHandler) + removeOnCancellation(node) return NonDisposableHandle } @@ -219,8 +219,16 @@ public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNod * Removes a given node on cancellation. * @suppress **This is unstable API and it is subject to change.** */ -public fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode): Unit = - invokeOnCancellation(handler = RemoveOnCancel(this as CancellableContinuationImpl<*>, node).asHandler) +public fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode): Unit { + val handler: CompletionHandler + if (this is CancellableContinuationImpl<*>) { + handler = RemoveOnCancel(this, node).asHandler + } else { + handler = { node.remove() } + } + + invokeOnCancellation(handler) +} /** * Disposes a specified [handle] when this continuation is cancelled. @@ -235,7 +243,7 @@ public fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedL replaceWith = ReplaceWith("disposeOnCancellation(handler)"), level = DeprecationLevel.HIDDEN) public fun CancellableContinuation<*>.disposeOnCompletion(handle: DisposableHandle): DisposableHandle { - invokeOnCancellation(handler = DisposeOnCancellation(this as CancellableContinuationImpl<*>, handle).asHandler) + disposeOnCancellation(handle) return NonDisposableHandle } @@ -247,8 +255,17 @@ public fun CancellableContinuation<*>.disposeOnCompletion(handle: DisposableHand * invokeOnCancellation { handle.dispose() } * ``` */ -public fun CancellableContinuation<*>.disposeOnCancellation(handle: DisposableHandle) = - invokeOnCancellation(handler = DisposeOnCancellation(this as CancellableContinuationImpl<*>, handle).asHandler) +public fun CancellableContinuation<*>.disposeOnCancellation(handle: DisposableHandle) { + val handler: CompletionHandler + if (this is CancellableContinuationImpl<*>) { + handler = DisposeOnCancellation(this, handle).asHandler + } else { + handler = { handle.dispose() } + } + + + invokeOnCancellation(handler) +} // --------------- implementation details --------------- @@ -259,8 +276,7 @@ private class RemoveOnCancel( ) : CancellationHandlerImpl>(cont) { override fun invoke(cause: Throwable?) { - if (continuation.isCancelled) - node.remove() + node.remove() } override fun toString() = "RemoveOnCancel[$node]" @@ -299,7 +315,7 @@ internal class CancellableContinuationImpl( is NotCompleted -> { val update: Any? = if (idempotent == null) value else CompletedIdempotentResult(idempotent, value, state) - if (tryUpdateState(state, update)) return state + if (tryUpdateStateToFinal(state, update)) return state } is CompletedIdempotentResult -> { if (state.idempotentResume === idempotent) { @@ -317,16 +333,14 @@ internal class CancellableContinuationImpl( loopOnState { state -> when (state) { is NotCompleted -> { - if (tryUpdateState(state, CompletedExceptionally(exception))) return state + if (tryUpdateStateToFinal(state, CompletedExceptionally(exception))) return state } else -> return null // cannot resume -- not active anymore } } } - override fun completeResume(token: Any) { - completeUpdateState(token as NotCompleted, state, resumeMode) - } + override fun completeResume(token: Any) = completeStateUpdate(token as NotCompleted, state, resumeMode) override fun CoroutineDispatcher.resumeUndispatched(value: T) { val dc = delegate as? DispatchedContinuation diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt index ce2659a1b6..f345e69aa8 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt @@ -124,7 +124,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 /** * @suppress **This is unstable API and it is subject to change.** */ - internal inline fun loopOnState(block: (Any?) -> Unit): Nothing { + private inline fun loopOnState(block: (Any?) -> Unit): Nothing { while (true) { block(state) } @@ -148,7 +148,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 * 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 { + private fun updateState(expect: Incomplete, proposedUpdate: Any?, mode: Int): Boolean { val update = coerceProposedUpdate(expect, proposedUpdate) if (!tryUpdateState(expect, update)) return false completeUpdateState(expect, update, mode) @@ -181,7 +181,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 * 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 { + private 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 @@ -196,7 +196,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 * 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) { + private fun completeUpdateState(expect: Incomplete, update: Any?, mode: Int) { val exceptionally = update as? CompletedExceptionally // Do overridable processing before completion handlers if (!expect.isCancelling) onCancellationInternal(exceptionally) // only notify when was not cancelling before @@ -338,7 +338,6 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 promoteSingleToNodeList(state as JobNode<*>) } else { if (state is Finishing && state.cancelled != null && onCancelling) { - check(onCancelMode != ON_CANCEL_MAKE_CANCELLED) // cannot be in this state unless were support cancelling state // installing cancellation handler on job that is being cancelled if (invokeImmediately) handler(state.cancelled.cause) return NonDisposableHandle @@ -358,12 +357,11 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 } private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode<*> { - val hasCancellingState = onCancelMode != ON_CANCEL_MAKE_CANCELLED - return if (onCancelling && hasCancellingState) + return 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)) } + (handler as? JobNode<*>)?.also { require(it.job === this && it !is JobCancellationNode) } ?: InvokeOnCompletion(this, handler) } @@ -458,7 +456,6 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 internal open val onCancelMode: Int get() = ON_CANCEL_MAKE_CANCELLING public override fun cancel(cause: Throwable?): Boolean = when (onCancelMode) { - ON_CANCEL_MAKE_CANCELLED -> makeCancelled(cause) ON_CANCEL_MAKE_CANCELLING -> makeCancelling(cause) ON_CANCEL_MAKE_COMPLETING -> makeCompletingOnCancel(cause) else -> error("Invalid onCancelMode $onCancelMode") @@ -469,14 +466,6 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 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 -> @@ -772,7 +761,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 } private suspend fun awaitSuspend(): Any? = suspendCancellableCoroutine { cont -> - // We have to invoke await() ha ndler only on cancellation, on completion we will be resumed regularly without handlers + // We have to invoke await() handler only on cancellation, on completion we will be resumed regularly without handlers cont.disposeOnCancellation(invokeOnCompletion { val state = this.state check(state !is Incomplete) @@ -827,7 +816,6 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 // --------------- helper classes to simplify job implementation -internal const val ON_CANCEL_MAKE_CANCELLED = 0 internal const val ON_CANCEL_MAKE_CANCELLING = 1 internal const val ON_CANCEL_MAKE_COMPLETING = 2 diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt index 3490d14ae9..a8fa9beb4d 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt @@ -76,9 +76,9 @@ private class CancelFutureOnCompletion( } private class CancelFutureOnCancellation( - continuation: CancellableContinuation<*>, + continuation: AbstractContinuation<*>, private val future: Future<*> -) : CancellationHandlerImpl>(continuation as AbstractContinuation<*>) { +) : CancellationHandlerImpl>(continuation) { override fun invoke(reason: Throwable?) { // Don't interrupt when cancelling future on completion, because no one is going to reset this diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCancellationStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCancellationStressTest.kt index e028fbf8cf..32d9eeb806 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCancellationStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithContextCancellationStressTest.kt @@ -19,7 +19,7 @@ class WithContextCancellationStressTest : TestBase() { fun testConcurrentCancellation() = runBlocking { var ioException = 0 var arithmeticException = 0 - var aiobException = 0 + var aioobException = 0 repeat(iterations) { val barrier = CyclicBarrier(4) @@ -54,11 +54,11 @@ class WithContextCancellationStressTest : TestBase() { val cause = e.cause when (cause) { is ArithmeticException -> ++arithmeticException - is ArrayIndexOutOfBoundsException -> ++aiobException + is ArrayIndexOutOfBoundsException -> ++aioobException else -> error("Unexpected exception") } } - else -> error("Unexpected exception") + else -> error("Unexpected exception $e") } } } @@ -66,7 +66,7 @@ class WithContextCancellationStressTest : TestBase() { // Backward compatibility, no exceptional code paths were lost require(ioException > 0) { "At least one IOException expected" } require(arithmeticException > 0) { "At least one ArithmeticException expected" } - require(aiobException > 0) { "At least one ArrayIndexOutOfBoundsException expected" } + require(aioobException > 0) { "At least one ArrayIndexOutOfBoundsException expected" } } private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext { From b1a07eeebefa14e6b06c46fcbef971102ef5490d Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Mon, 16 Apr 2018 21:51:55 +0300 Subject: [PATCH 31/61] Setup dependency resolution strategy to force kotlin_version - Use 'kotlin_version' for all dependencies from 'org.jetbrains.kotlin' group except 'kotlin-native-gradle-plugin', even if other dependencies bring newer versions as transitive - Add maven snapshot repository to subprojects - Override 'kotlin_version' from gradle.properties in all projects when kotlinSnapshot property is set - Place buildscript block first as it executes always first. --- build.gradle | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 32632ff7ad..ddc65a5d6c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,6 @@ -allprojects { - def deployVersion = properties['DeployVersion'] - if (deployVersion != null) version = deployVersion -} - buildscript { - if (rootProject.properties['kotlinSnapshot'] != null) { + ext.useKotlinSnapshot = rootProject.properties['kotlinSnapshot'] != null + if (useKotlinSnapshot) { ext.kotlin_version = '1.2-SNAPSHOT' repositories { maven { url "https://oss.sonatype.org/content/repositories/snapshots" } @@ -17,6 +13,18 @@ buildscript { maven { url "https://jetbrains.bintray.com/kotlin-native-dependencies" } maven { url "https://plugins.gradle.org/m2/" } } + configurations.classpath { + resolutionStrategy { + eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'org.jetbrains.kotlin' && details.requested.name != 'kotlin-native-gradle-plugin') { + // fix version of all dependencies from org.jetbrains.kotlin group + // even when other dependencies require other versions indirectly, + // except kotlin-native, which has its own pre-release versioning + details.useVersion kotlin_version + } + } + } + } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_version" @@ -27,6 +35,15 @@ buildscript { } } +allprojects { + def deployVersion = properties['DeployVersion'] + if (deployVersion != null) version = deployVersion + if (useKotlinSnapshot) { + kotlin_version = '1.2-SNAPSHOT' + } +} + + // Report Kotlin compiler version when building project println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION") @@ -46,6 +63,23 @@ static def platformLib(base, platform) { return "$base-$platform" } +subprojects { + if (useKotlinSnapshot) { + repositories { + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + } + } + configurations.all { + resolutionStrategy { + eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'org.jetbrains.kotlin') { + details.useVersion kotlin_version + } + } + } + } +} + configure(subprojects.findAll { !sourceless.contains(it.name) }) { def platform = platformOf(it) apply from: rootProject.file("gradle/compile-${platform}.gradle") From 480d8e9cfe24c3e29810e7095ece6c111fd3ee49 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Mon, 16 Apr 2018 21:54:13 +0300 Subject: [PATCH 32/61] Fix complaints of the latest compiler: actuals without expect --- .../src/main/kotlin/kotlinx/coroutines/experimental/Job.kt | 2 +- .../coroutines/experimental/internal/LockFreeLinkedList.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt index e94e951b29..7cb17a5f5b 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt @@ -1332,7 +1332,7 @@ internal interface Incomplete { val list: NodeList? // is null only for Empty and JobNode incomplete state objects } -internal abstract class JobNode actual constructor( +internal abstract class JobNode( @JvmField val job: J ) : CompletionHandlerNode(), DisposableHandle, Incomplete { override val isActive: Boolean get() = true diff --git a/core/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 index 17a0ebbc7c..057ce9d43a 100644 --- a/core/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 @@ -311,7 +311,7 @@ public actual open class LockFreeLinkedListNode { // ------ multi-word atomic operations helpers ------ - public open class AddLastDesc actual constructor( + public open class AddLastDesc( @JvmField val queue: Node, @JvmField val node: T ) : AbstractAtomicDesc() { From f0cd180aa2f3846f10db465f25f3b1c541faf472 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Tue, 17 Apr 2018 19:59:31 +0300 Subject: [PATCH 33/61] Suppress errors about default parameters in actual functions Need to cleanup these suppressions after migrating to Kotlin 1.2.40. --- .../kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt | 1 + .../test/kotlin/kotlinx/coroutines/experimental/TestBase.kt | 3 ++- .../kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt | 1 + .../test/kotlin/kotlinx/coroutines/experimental/TestBase.kt | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt index c601a0535e..820d87a5da 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt @@ -89,6 +89,7 @@ public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool * The string "coroutine" is used as a default name. */ @JvmOverloads // for binary compatibility with newCoroutineContext(context: CoroutineContext) version +@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext { val debug = if (DEBUG) context + CoroutineId(COROUTINE_ID.incrementAndGet()) else context val wp = if (parent == null) debug else debug + parent diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt index f6f087b38a..03a697a325 100644 --- a/core/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,6 +62,7 @@ public actual open class TestBase actual constructor() { * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not * complete successfully even if this exception is consumed somewhere in the test. */ + @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun error(message: Any, cause: Throwable? = null): Nothing { val exception = IllegalStateException(message.toString(), cause) error.compareAndSet(null, exception) @@ -117,7 +118,7 @@ public actual open class TestBase actual constructor() { checkTestThreads(threadsBefore) } - @Suppress("ACTUAL_WITHOUT_EXPECT") + @Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun runTest( expected: ((Throwable) -> Boolean)? = null, unhandled: List<(Throwable) -> Boolean> = emptyList(), diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt index 6d1cbd894c..d6fdf5df66 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt @@ -47,6 +47,7 @@ internal actual val DefaultDelay: Delay = DefaultDispatcher as Delay * 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). */ +@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext { val wp = if (parent == null) context else context + parent return if (context !== DefaultDispatcher && context[ContinuationInterceptor] == null) diff --git a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt index 7a3da049e8..3cc9b206f7 100644 --- a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt +++ b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt @@ -30,6 +30,7 @@ public actual open class TestBase actual constructor() { * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not * complete successfully even if this exception is consumed somewhere in the test. */ + @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun error(message: Any, cause: Throwable? = null): Nothing { if (cause != null) console.log(cause) val exception = IllegalStateException( @@ -63,6 +64,7 @@ public actual open class TestBase actual constructor() { } // todo: The dynamic (promise) result is a work-around for missing suspend tests, see KT-22228 + @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun runTest( expected: ((Throwable) -> Boolean)? = null, unhandled: List<(Throwable) -> Boolean> = emptyList(), From 81f79c3bfb6e889bf4f43096f6ad0d268e0ae126 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Tue, 17 Apr 2018 19:59:50 +0300 Subject: [PATCH 34/61] Do not use deprecated coroutineContext --- .../coroutines/experimental/io/internal/InlineRendezvousSwap.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/InlineRendezvousSwap.kt b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/InlineRendezvousSwap.kt index 1a83ac78ed..a78ad07d4f 100644 --- a/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/InlineRendezvousSwap.kt +++ b/core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/InlineRendezvousSwap.kt @@ -1,6 +1,7 @@ package kotlinx.coroutines.experimental.io.internal import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.coroutineContext import kotlin.coroutines.experimental.intrinsics.* /** From cc084269c799aecfe3317f2dfdf04b334d77cd23 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Wed, 18 Apr 2018 18:16:17 +0300 Subject: [PATCH 35/61] Do not put kotlin dependency in package.json used for tests kotlin.js is being extracted from kotlin-stdlib-js by populateNodeModules task, therefore getting it from npm is not necessary and fails when SNAPSHOT dependency was used. --- gradle/node-js.gradle | 2 +- gradle/publish-npm-js.gradle | 2 +- js/kotlinx-coroutines-core-js/npm/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/node-js.gradle b/gradle/node-js.gradle index 7c5f8748a6..640615bacd 100644 --- a/gradle/node-js.gradle +++ b/gradle/node-js.gradle @@ -13,7 +13,7 @@ node { task prepareNodePackage(type: Copy) { from("npm") { include 'package.json' - expand project.properties + expand (project.properties + [kotlinDependency: ""]) } from("npm") { exclude 'package.json' diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle index b02bfd8b85..714f185a17 100644 --- a/gradle/publish-npm-js.gradle +++ b/gradle/publish-npm-js.gradle @@ -20,7 +20,7 @@ def dryRun = prop("dryRun", "false") task preparePublishNpm(type: Copy, dependsOn: [compileKotlin2Js]) { from(npmTemplateDir) { - expand project.properties + expand (project.properties + [kotlinDependency: "\"kotlin\": \"$kotlin_version\""]) } from("$buildDir/classes/kotlin/main") into npmDeployDir diff --git a/js/kotlinx-coroutines-core-js/npm/package.json b/js/kotlinx-coroutines-core-js/npm/package.json index c11b0f32bb..ae1652d46f 100644 --- a/js/kotlinx-coroutines-core-js/npm/package.json +++ b/js/kotlinx-coroutines-core-js/npm/package.json @@ -21,6 +21,6 @@ "JetBrains" ], "dependencies": { - "kotlin": "$kotlin_version" + $kotlinDependency } } From 58fa752017144bb380a18ef1eaee04e622f32f56 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Wed, 18 Apr 2018 18:21:02 +0300 Subject: [PATCH 36/61] Do not print npmPublish args at configuration phase --- gradle/publish-npm-js.gradle | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle index 714f185a17..dbf7b64c2f 100644 --- a/gradle/publish-npm-js.gradle +++ b/gradle/publish-npm-js.gradle @@ -29,12 +29,14 @@ task preparePublishNpm(type: Copy, dependsOn: [compileKotlin2Js]) { task publishNpm(type: NpmTask, dependsOn: [preparePublishNpm]) { workingDir = npmDeployDir def deployArgs = ['publish', - "--//registry.npmjs.org/:_authToken=$authToken", - "--tag=$npmDeployTag"] - if (dryRun == "true") { - println("$npmDeployDir \$ npm arguments: $deployArgs") - args = ['pack'] - } else { - args = deployArgs + "--//registry.npmjs.org/:_authToken=$authToken", + "--tag=$npmDeployTag"] + doFirst { + if (dryRun == "true") { + println("$npmDeployDir \$ npm arguments: $deployArgs") + args = ['pack'] + } else { + args = deployArgs + } } } From f4eb05a31a6ce7634b31e7990e24a2f73e2f5792 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 25 Apr 2018 11:56:22 +0300 Subject: [PATCH 37/61] Kotlin 1.2.40 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a062258569..109b0e22a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ version = 0.22.5-SNAPSHOT group = org.jetbrains.kotlinx -kotlin_version = 1.2.31 +kotlin_version = 1.2.40 kotlin_native_version = 0.7-dev-1436 junit_version = 4.12 atomicFU_version = 0.10.0-alpha From 4b9a559ece4f40b471f1757b15abee34016cf18f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 11 Apr 2018 13:17:14 +0300 Subject: [PATCH 38/61] CompletedExceptionally now always has cause and exception became an alias to a cause. CompletionHandler receives JobCancellationException if it was invoked during regular cancellation. Test coverage for cancellation paths improved. --- .../experimental/CompletedExceptionally.kt | 44 ++---- .../kotlinx/coroutines/experimental/Job.kt | 14 +- .../experimental/channels/Produce.kt | 3 +- .../experimental/channels/ActorTest.kt | 64 +++++++- .../channels/ArrayBroadcastChannelTest.kt | 11 +- .../experimental/channels/ArrayChannelTest.kt | 10 +- .../channels/ConflatedChannelTest.kt | 10 +- .../channels/LinkedListChannelTest.kt | 10 +- .../experimental/channels/ProduceTest.kt | 39 ++++- .../channels/RendezvousChannelTest.kt | 8 + .../experimental/io/ByteChannelCloseTest.kt | 140 ++++++++++++++++++ .../experimental/io/CopyAndCloseTest.kt | 33 ----- 12 files changed, 306 insertions(+), 80 deletions(-) create mode 100644 core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteChannelCloseTest.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt index 8e3276e24d..9e37586db9 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt @@ -23,36 +23,21 @@ import kotlinx.coroutines.experimental.internalAnnotations.* * * **Note: This class cannot be used outside of internal coroutines framework**. * - * @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. + * @param cause the exceptional completion cause. It's either original exceptional cause + * or artificial JobCancellationException if no cause was provided * @suppress **This is unstable API and it is subject to change.** */ -public open class CompletedExceptionally protected constructor( - @JvmField public 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" } - } +public open class CompletedExceptionally(public val cause: Throwable) { /** * Returns completion exception. */ - public val exception: Throwable get() = - _exception ?: // atomic read volatile var or else create new - createException().also { _exception = it } + public val exception: Throwable get() = cause // alias for backward compatibility - protected open fun createException(): Throwable = error("Completion exception was not specified") + /** + * Returns true if the current exceptional reason is cancellation without cause + */ + open fun isCancelledWithoutCause() = false override fun toString(): String = "$classSimpleName[$exception]" } @@ -61,16 +46,15 @@ public open class CompletedExceptionally protected constructor( * A specific subclass of [CompletedExceptionally] for cancelled jobs. * * **Note: This class cannot be used outside of internal coroutines framework**. - * + * * @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. + * @param cause the exceptional completion cause. If `cause` is null, then a [JobCancellationException] is created. * @suppress **This is unstable API and it is subject to change.** */ public class Cancelled( - private val job: Job, - cause: Throwable? -) : CompletedExceptionally(cause, true) { - override fun createException(): Throwable = JobCancellationException("Job was cancelled normally", null, job) + private val job: Job, + cause: Throwable?) : CompletedExceptionally(cause ?: JobCancellationException("Job was cancelled normally", null, job)) { + + override fun isCancelledWithoutCause(): Boolean = cause is JobCancellationException && cause.job == job } diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt index 7cb17a5f5b..ca5b6d1b2b 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt @@ -642,14 +642,16 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 if (proposedUpdate !is Cancelled) return false // NOTE: equality comparison of causes is performed here by design, see equals of JobCancellationException return proposedUpdate.cause == cancelled.cause || - proposedUpdate.cause is JobCancellationException && cancelled.cause == null + proposedUpdate.cause is JobCancellationException && cancelled.isCancelledWithoutCause() } private fun createCancelled(cancelled: Cancelled, proposedUpdate: Any?): Cancelled { if (proposedUpdate !is CompletedExceptionally) return cancelled // not exception -- just use original cancelled val exception = proposedUpdate.exception if (cancelled.exception == exception) return cancelled // that is the cancelled we need already! - cancelled.cause?.let { exception.addSuppressedThrowable(it) } + if (!cancelled.isCancelledWithoutCause()) { + exception.addSuppressedThrowable(cancelled.cause) + } return Cancelled(this, exception) } @@ -772,7 +774,13 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 protected fun getCompletionCause(): Throwable? { val state = this.state return when { - state is Finishing && state.cancelled != null -> state.cancelled.cause + state is Finishing && state.cancelled != null -> { + if (state.cancelled.isCancelledWithoutCause()) { + null + } else { + state.cancelled.cause + } + } state is Incomplete -> error("Job was not completed or cancelled yet") state is CompletedExceptionally -> state.cause else -> null diff --git a/core/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 index fc86b0aa7a..d19c26e5f7 100644 --- a/core/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 @@ -123,7 +123,8 @@ private class ProducerCoroutine( override fun onCancellationInternal(exceptionally: CompletedExceptionally?) { val cause = exceptionally?.cause val processed = when (exceptionally) { - is Cancelled -> _channel.cancel(cause) // producer coroutine was cancelled -- cancel channel + // producer coroutine was cancelled -- cancel channel, but without cause if it was closed without cause + is Cancelled -> _channel.cancel(if (exceptionally.isCancelledWithoutCause()) null else cause) else -> _channel.close(cause) // producer coroutine has completed -- close channel } if (!processed && cause != null) diff --git a/core/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 index 1494d2f653..3032944ab4 100644 --- a/core/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 @@ -20,13 +20,24 @@ import kotlinx.coroutines.experimental.* import org.hamcrest.core.* import org.junit.* import org.junit.Assert.* +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.io.IOException import kotlin.coroutines.experimental.* -class ActorTest : TestBase() { +@RunWith(Parameterized::class) +class ActorTest(private val capacity: Int) : TestBase() { + + companion object { + @Parameterized.Parameters(name = "Capacity: {0}") + @JvmStatic + fun params(): Collection> = listOf(0, 1, Channel.UNLIMITED, Channel.CONFLATED).map { arrayOf(it) } + } + @Test - fun testEmpty() = runBlocking { + fun testEmpty() = runBlocking { expect(1) - val actor = actor(coroutineContext) { + val actor = actor(coroutineContext, capacity) { expect(3) } actor as Job // type assertion @@ -42,9 +53,9 @@ class ActorTest : TestBase() { } @Test - fun testOne() = runBlocking { + fun testOne() = runBlocking { expect(1) - val actor = actor(coroutineContext) { + val actor = actor(coroutineContext, capacity) { expect(3) assertThat(receive(), IsEqual("OK")) expect(6) @@ -68,4 +79,45 @@ class ActorTest : TestBase() { assertThat(actor.isClosedForSend, IsEqual(true)) finish(7) } -} \ No newline at end of file + + + @Test + fun testCancelWithoutCause() = runTest { + val actor = actor(coroutineContext, capacity) { + val element = channel.receiveOrNull() + expect(2) + assertEquals(42, element) + val next = channel.receiveOrNull() + assertNull(next) + expect(3) + } + + expect(1) + actor.send(42) + yield() + actor.close() + yield() + finish(4) + } + + @Test + fun testCancelWithCause() = runTest { + val actor = actor(coroutineContext, capacity) { + val element = channel.receiveOrNull() + expect(2) + require(element!! == 42) + try { + val next = channel.receiveOrNull() + } catch (e: IOException) { + expect(3) + } + } + + expect(1) + actor.send(42) + yield() + actor.close(IOException()) + yield() + finish(4) + } +} diff --git a/core/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 index a71e3f8a56..f48c787844 100644 --- a/core/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 @@ -20,6 +20,7 @@ import kotlinx.coroutines.experimental.* import org.hamcrest.core.* import org.junit.* import org.junit.Assert.* +import java.io.IOException import kotlin.coroutines.experimental.* class ArrayBroadcastChannelTest : TestBase() { @@ -178,4 +179,12 @@ class ArrayBroadcastChannelTest : TestBase() { assertTrue(sub.isClosedForReceive) sub.receive() } -} \ No newline at end of file + + @Test(expected = IOException::class) + fun testCancelWithCause() = runBlocking { + val channel = BroadcastChannel(1) + val subscription = channel.openSubscription() + subscription.cancel(IOException()) + subscription.receiveOrNull() + } +} diff --git a/core/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 index 80d3682127..fa1b3c8884 100644 --- a/core/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 @@ -19,6 +19,7 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* import org.junit.* import org.junit.Assert.* +import java.io.IOException import kotlin.coroutines.experimental.* class ArrayChannelTest : TestBase() { @@ -168,4 +169,11 @@ class ArrayChannelTest : TestBase() { check(q.receiveOrNull() == null) finish(12) } -} \ No newline at end of file + + @Test(expected = IOException::class) + fun testCancelWithCause() = runBlocking { + val channel = ArrayChannel(5) + channel.cancel(IOException()) + channel.receiveOrNull() + } +} diff --git a/core/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 index 6819170f0e..b6eafbd07e 100644 --- a/core/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 @@ -20,6 +20,7 @@ import kotlinx.coroutines.experimental.* import org.hamcrest.core.* import org.junit.* import org.junit.Assert.* +import java.io.IOException import kotlin.coroutines.experimental.* class ConflatedChannelTest : TestBase() { @@ -91,4 +92,11 @@ class ConflatedChannelTest : TestBase() { check(q.receiveOrNull() == null) finish(2) } -} \ No newline at end of file + + @Test(expected = IOException::class) + fun testCancelWithCause() = runBlocking { + val channel = ConflatedChannel() + channel.cancel(IOException()) + channel.receiveOrNull() + } +} diff --git a/core/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 index 95a6c119d9..0b5f16929a 100644 --- a/core/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 @@ -22,6 +22,7 @@ import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.core.IsEqual import org.hamcrest.core.IsNull import org.junit.Test +import java.io.IOException class LinkedListChannelTest : TestBase() { @Test @@ -49,4 +50,11 @@ class LinkedListChannelTest : TestBase() { check(q.isClosedForReceive) check(q.receiveOrNull() == null) } -} \ No newline at end of file + + @Test(expected = IOException::class) + fun testCancelWithCause() = runBlocking { + val channel = LinkedListChannel() + channel.cancel(IOException()) + channel.receiveOrNull() + } +} 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 index 641eff0f62..969284e10c 100644 --- 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 @@ -18,9 +18,12 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* import org.junit.* +import java.io.IOException import kotlin.coroutines.experimental.* +import kotlin.test.assertNull class ProduceTest : TestBase() { + @Test fun testBasic() = runTest { val c = produce(coroutineContext) { @@ -40,7 +43,7 @@ class ProduceTest : TestBase() { } @Test - fun testCancel() = runTest { + fun testCancelWithoutCause() = runTest { val c = produce(coroutineContext) { expect(2) send(1) @@ -59,7 +62,37 @@ class ProduceTest : TestBase() { expect(4) c.cancel() expect(5) - check(c.receiveOrNull() == null) + assertNull(c.receiveOrNull()) expect(6) } -} \ No newline at end of file + + @Test + fun testCancelWithCause() = runTest { + val c = produce(coroutineContext) { + expect(2) + send(1) + expect(3) + try { + send(2) // will get cancelled + } catch (e: Exception) { + finish(6) + check(e is JobCancellationException && e.job == coroutineContext[Job]) + check(e.cause is IOException) + throw e + } + expectUnreached() + } + + expect(1) + check(c.receive() == 1) + expect(4) + c.cancel(IOException()) + + try { + assertNull(c.receiveOrNull()) + expectUnreached() + } catch (e: IOException) { + expect(5) + } + } +} diff --git a/core/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 index 7edbe42bf6..eaf1993ae4 100644 --- a/core/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 @@ -20,6 +20,7 @@ import kotlinx.coroutines.experimental.* import org.hamcrest.core.* import org.junit.* import org.junit.Assert.* +import java.io.IOException import kotlin.coroutines.experimental.* class RendezvousChannelTest : TestBase() { @@ -308,4 +309,11 @@ class RendezvousChannelTest : TestBase() { check(q.receiveOrNull() == null) finish(12) } + + @Test(expected = IOException::class) + fun testCancelWithCause() = runBlocking { + val channel = RendezvousChannel() + channel.cancel(IOException()) + channel.receiveOrNull() + } } \ No newline at end of file diff --git a/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteChannelCloseTest.kt b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteChannelCloseTest.kt new file mode 100644 index 0000000000..91f5c8bd23 --- /dev/null +++ b/core/kotlinx-coroutines-io/src/test/kotlin/kotlinx/coroutines/experimental/io/ByteChannelCloseTest.kt @@ -0,0 +1,140 @@ +package kotlinx.coroutines.experimental.io + +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException +import org.junit.After +import org.junit.Test +import java.io.IOException +import kotlin.coroutines.experimental.coroutineContext + +class ByteChannelCloseTest : TestBase() { + + private val from = ByteChannel(true) + private val to = ByteChannel(true) + + @After + fun tearDown() { + from.close(CancellationException()) + to.close(CancellationException()) + } + + @Test + fun testCloseWithCause() = runBlocking { + expect(1) + + launch(coroutineContext) { + expect(2) + + try { + from.copyAndClose(to) // should suspend and then throw IOException + expectUnreached() + } catch (expected: IOException) { + expect(4) + } + } + + yield() + expect(3) + + from.close(IOException()) + yield() + + expect(5) + + try { + to.readInt() + expectUnreached() + } catch (expected: IOException) { + finish(6) + } + } + + @Test + fun testCancelWithCause() = runBlocking { + expect(1) + + launch(coroutineContext) { + expect(2) + + try { + from.copyAndClose(to) // should suspend and then throw IOException + expectUnreached() + } catch (expected: IOException) { + expect(4) + } + } + + yield() + expect(3) + + from.cancel(IOException()) + yield() + + expect(5) + + try { + to.readInt() + expectUnreached() + } catch (expected: IOException) { + finish(6) + } + } + + @Test + fun testCloseWithoutCause() = runBlocking { + expect(1) + + launch(coroutineContext) { + expect(2) + from.copyAndClose(to) + expect(4) + } + + yield() + expect(3) + + from.close() + yield() + + expect(5) + require(to.isClosedForWrite) + + try { + to.readInt() + expectUnreached() + } catch (expected: ClosedReceiveChannelException) { + finish(6) + } + } + + @Test + fun testCancelWithoutCause() = runBlocking { + expect(1) + + launch(coroutineContext) { + expect(2) + try { + from.copyAndClose(to) + expectUnreached() + } catch (e: CancellationException) { + expect(4) + } + } + + yield() + expect(3) + + from.cancel() + yield() + + expect(5) + require(to.isClosedForWrite) + + try { + to.readInt() + expectUnreached() + } catch (expected: CancellationException) { + finish(6) + } + } +} 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 index 504a10352b..00abc7c9f4 100644 --- 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 @@ -48,39 +48,6 @@ class CopyAndCloseTest : TestBase() { 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) From 4f0d48b5ce908bea83555ee451fca865e280b7dd Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 11 Apr 2018 13:51:33 +0300 Subject: [PATCH 39/61] Introducing awaitAll and joinAll extensions on JVM --- .../kotlinx/coroutines/experimental/Await.kt | 66 ++++ .../experimental/AwaitStressTest.kt | 103 ++++++ .../coroutines/experimental/AwaitTest.kt | 321 ++++++++++++++++++ 3 files changed, 490 insertions(+) create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt new file mode 100644 index 0000000000..042237ba38 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -0,0 +1,66 @@ +package kotlinx.coroutines.experimental + +import kotlinx.atomicfu.atomic + +/** + * Awaits for completion of given jobs without blocking a thread and resumes normally when all deferred computations are complete + * or resumes with the first thrown exception if any of computations completes exceptionally including cancellation. + * This suspending function is cancellable. + * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, + * this function immediately resumes with [CancellationException]. + */ +suspend fun awaitAll(vararg jobs: Job): Unit = jobs.asList().awaitAll() + +/** + * Awaits for completion of given jobs without blocking a thread and resumes normally when all jobs computations are complete + * or resumes with the first thrown exception if any of computations completes exceptionally including cancellation. + * This suspending function is cancellable. + * If the [Job] of the current coroutine is l or completed while this suspending function is waiting, + * this function immediately resumes with [CancellationException]. + */ +suspend fun Iterable.awaitAll(): Unit { + val jobs = this as? Collection ?: this.toList() + if (jobs.isEmpty()) { + return + } + + AwaitAll(jobs).await() +} + +/** + * Suspends current coroutine until all given jobs are complete. This method is semantically equivalent to joining all given jobs one by one. + * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, + * this function immediately resumes with [CancellationException]. + */ +suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() } + +/** + * Suspends current coroutine until all given jobs are complete. This method is semantically equivalent to + * joining all given jobs one by one. + * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, + * this function immediately resumes with [CancellationException]. + */ +suspend fun Iterable.joinAll(): Unit = forEach { it.join() } + +private class AwaitAll(private val jobs: Collection) { + private val notCompletedCount = atomic(jobs.size) + + suspend fun await() { + suspendCancellableCoroutine { cont -> + val handler: (Throwable?) -> Unit = { + if (it != null) { + val token = cont.tryResumeWithException(it) + if (token != null) { + cont.completeResume(token) + } + } else if (notCompletedCount.decrementAndGet() == 0) { + cont.resume(Unit) + } + } + + jobs.forEach { + cont.disposeOnCompletion(it.invokeOnCompletion(handler)) + } + } + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt new file mode 100644 index 0000000000..79869d7402 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt @@ -0,0 +1,103 @@ +package kotlinx.coroutines.experimental + +import org.junit.After +import java.util.concurrent.CyclicBarrier +import kotlin.test.Test + +class AwaitStressTest : TestBase() { + + private class TestException : Exception() { + override fun fillInStackTrace(): Throwable = this + } + + private val iterations = 50_000 + private val pool = newFixedThreadPoolContext(4, "AwaitStressTest") + + @After + fun tearDown() { + pool.close() + } + + @Test + fun testMultipleExceptions() = runTest { + + repeat(iterations) { + val barrier = CyclicBarrier(4) + + val d1 = async(pool) { + barrier.await() + throw TestException() + } + + val d2 = async(pool) { + barrier.await() + throw TestException() + } + + val d3 = async(pool) { + barrier.await() + 1L + } + + try { + barrier.await() + awaitAll(d1, d2, d3) + expectUnreached() + } catch (e: TestException) { + // Expected behaviour + } + + barrier.reset() + } + } + + @Test + fun testAwaitAll() = runTest { + val barrier = CyclicBarrier(3) + + repeat(iterations) { + val d1 = async(pool) { + barrier.await() + 1L + } + + val d2 = async(pool) { + barrier.await() + 2L + } + + barrier.await() + awaitAll(d1, d2) + require(d1.isCompleted && d2.isCompleted) + barrier.reset() + } + } + + @Test + fun testConcurrentCancellation() = runTest { + var cancelledOnce = false + repeat(iterations) { + val barrier = CyclicBarrier(3) + + val d1 = async(pool) { + barrier.await() + delay(10_000) + yield() + } + + val d2 = async(pool) { + barrier.await() + d1.cancel() + } + + barrier.await() + try { + awaitAll(d1, d2) + } catch (e: JobCancellationException) { + cancelledOnce = true + } + } + + require(cancelledOnce) { "Cancellation exception wasn't properly caught" } + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt new file mode 100644 index 0000000000..c87f0abf9b --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt @@ -0,0 +1,321 @@ +package kotlinx.coroutines.experimental + +import kotlin.coroutines.experimental.coroutineContext +import kotlin.test.Test + +class AwaitTest : TestBase() { + + @Test + fun testAwaitAll() = runTest { + expect(1) + val d = async(coroutineContext) { + expect(3) + "OK" + } + + val d2 = async(coroutineContext) { + yield() + expect(4) + 1L + } + + expect(2) + require(d2.isActive && !d2.isCompleted) + + awaitAll(d, d2) + expect(5) + + require(d.isCompleted && d2.isCompleted) + require(!d.isCancelled && !d2.isCancelled) + finish(6) + } + + @Test + fun testAwaitAllTyped() = runTest { + val d1 = async(coroutineContext) { 1L } + val d2 = async(coroutineContext) { "" } + val d3 = launch(coroutineContext) { } + + setOf(d1, d2).awaitAll() + setOf(d1, d3).awaitAll() + listOf(d2, d3).awaitAll() + } + + @Test + fun testAwaitAllExceptionally() = runTest { + expect(1) + val d = async(coroutineContext) { + expect(3) + "OK" + } + + val d2 = async(coroutineContext) { + yield() + throw TestException() + } + + val d3 = async(coroutineContext) { + expect(4) + delay(Long.MAX_VALUE) + 1 + } + + expect(2) + try { + awaitAll(d, d2, d3) + } catch (e: TestException) { + expect(5) + } + + yield() + require(d.isCompleted && d2.isCompletedExceptionally && d3.isActive) + d3.cancel() + finish(6) + } + + @Test + fun testAwaitAllMultipleExceptions() = runTest { + val d = async(coroutineContext) { + expect(2) + throw TestException() + } + + val d2 = async(coroutineContext) { + yield() + throw TestException() + } + + val d3 = async(coroutineContext) { + yield() + } + + expect(1) + try { + awaitAll(d, d2, d3) + } catch (e: TestException) { + expect(3) + } + + finish(4) + } + + @Test + fun testAwaitAllCancellation() = runTest { + val outer = async(coroutineContext) { + + expect(1) + val inner = async(coroutineContext) { + expect(4) + delay(Long.MAX_VALUE) + } + + expect(2) + awaitAll(inner) + expectUnreached() + } + + yield() + expect(3) + yield() + require(outer.isActive) + outer.cancel() + require(outer.isCancelled) + finish(5) + } + + @Test + fun testAwaitAllPartiallyCompleted() = runTest { + val d1 = async(coroutineContext) { expect(1) } + d1.await() + val d2 = launch(coroutineContext) { expect(3) } + expect(2) + awaitAll(d1, d2) + require(d1.isCompleted && d2.isCompleted) + finish(4) + } + + @Test + fun testAwaitAllPartiallyCompletedExceptionally() = runTest { + val d1 = async(coroutineContext) { + expect(1) + throw TestException() + } + + yield() + + // This job is called after exception propagation + val d2 = async(coroutineContext) { expect(4) } + + expect(2) + try { + awaitAll(d1, d2) + expectUnreached() + } catch (e: TestException) { + expect(3) + } + + require(d2.isActive) + d2.await() + require(d1.isCompleted && d2.isCompleted) + finish(5) + } + + @Test + fun testAwaitAllFullyCompleted() = runTest { + val d1 = CompletableDeferred(Unit) + val d2 = CompletableDeferred(Unit) + val job = async(coroutineContext) { expect(3) } + expect(1) + awaitAll(d1, d2) + expect(2) + job.await() + finish(4) + } + + @Test + fun testAwaitOnSet() = runTest { + val d1 = CompletableDeferred(Unit) + val d2 = CompletableDeferred(Unit) + val job = async(coroutineContext) { expect(2) } + expect(1) + listOf(d1, d2, job).awaitAll() + finish(3) + } + + @Test + fun testAwaitAllFullyCompletedExceptionally() = runTest { + val d1 = CompletableDeferred(parent = null).apply { completeExceptionally(TestException()) } + val d2 = CompletableDeferred(parent = null).apply { completeExceptionally(TestException()) } + val job = async(coroutineContext) { expect(3) } + expect(1) + try { + awaitAll(d1, d2) + } catch (e: TestException) { + expect(2) + } + + job.await() + finish(4) + } + + @Test + fun testAwaitAllSameJobMultipleTimes() = runTest { + val job = launch(coroutineContext) { } + // Duplicates are allowed though kdoc doesn't guarantee that + awaitAll(job, job, job) + } + + @Test + fun testAwaitAllSameThrowingJobMultipleTimes() = runTest { + val d1 = async(coroutineContext) { throw TestException() } + val d2 = async(coroutineContext) { } // do nothing + + try { + expect(1) + // Duplicates are allowed though kdoc doesn't guarantee that + awaitAll(d1, d2, d1, d2) + expectUnreached() + } catch (e: TestException) { + finish(2) + } + } + + @Test + fun testAwaitAllEmpty() = runTest { + expect(1) + awaitAll() + emptyList().awaitAll() + finish(2) + } + + // joinAll + + @Test + fun testJoinAll() = runTest { + val d1 = launch(coroutineContext) { expect(2) } + val d2 = async(coroutineContext) { + expect(3) + "OK" + } + val d3 = launch(coroutineContext) { expect(4) } + + expect(1) + joinAll(d1, d2, d3) + finish(5) + } + + @Test + fun testJoinAllExceptionally() = runTest { + val d1 = launch(coroutineContext) { + expect(2) + } + val d2 = async(coroutineContext) { + expect(3) + throw TestException() + } + val d3 = async(coroutineContext) { + expect(4) + } + + expect(1) + joinAll(d1, d2, d3) + finish(5) + } + + @Test + fun testJoinAllCancellation() = runTest { + val outer = launch(coroutineContext) { + expect(2) + val inner = launch(coroutineContext) { + expect(3) + delay(Long.MAX_VALUE) + } + + joinAll(inner) + expectUnreached() + } + + expect(1) + yield() + require(outer.isActive) + yield() + outer.cancel() + outer.join() + finish(4) + } + + @Test + fun testJoinAllAlreadyCompleted() = runTest { + val job = launch(coroutineContext) { + expect(1) + } + + job.join() + expect(2) + + joinAll(job) + finish(3) + } + + @Test + fun testJoinAllEmpty() = runTest { + expect(1) + joinAll() + listOf().joinAll() + finish(2) + } + + @Test + fun testJoinAllSameJob() = runTest { + val job = launch(coroutineContext) { } + joinAll(job, job, job) + } + + @Test + fun testJoinAllSameJobExceptionally() = runTest { + val job = async(coroutineContext) { throw TestException() } + joinAll(job, job, job) + } + + private class TestException : Exception() +} From 9c692797a79a054f9e51e3528101b90af6e2c1bb Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 11 Apr 2018 18:22:35 +0300 Subject: [PATCH 40/61] Update readme, take the shortest link in Knit when resolving API references --- core/kotlinx-coroutines-core/README.md | 18 +++++---- .../kotlinx/coroutines/experimental/Await.kt | 8 ++-- knit/src/Knit.kt | 38 +++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/core/kotlinx-coroutines-core/README.md b/core/kotlinx-coroutines-core/README.md index 1e1c7226a8..2d21725022 100644 --- a/core/kotlinx-coroutines-core/README.md +++ b/core/kotlinx-coroutines-core/README.md @@ -39,13 +39,15 @@ Synchronization primitives for coroutines: Top-level suspending functions: -| **Name** | **Description** -| ------------------- | --------------- -| [delay] | Non-blocking sleep -| [yield] | Yields thread in single-threaded dispatchers -| [withContext] | Switches to a different context -| [withTimeout] | Set execution time-limit with exception on timeout -| [withTimeoutOrNull] | Set execution time-limit will null result on timeout +| **Name** | **Description** +| ------------------- | --------------- +| [delay] | Non-blocking sleep +| [yield] | Yields thread in single-threaded dispatchers +| [withContext] | Switches to a different context +| [withTimeout] | Set execution time-limit with exception on timeout +| [withTimeoutOrNull] | Set execution time-limit will null result on timeout +| [awaitAll] | Awaits for successful completion of all given jobs or exceptional completion of any +| [joinAll] | Joins on all given jobs [Select][kotlinx.coroutines.experimental.selects.select] expression waits for the result of multiple suspending functions simultaneously: @@ -113,6 +115,8 @@ Optional time unit support for multiplatform projects. [withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-context.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 +[awaitAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/await-all.html +[joinAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/join-all.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 diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt index 042237ba38..873b3b4dea 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -9,7 +9,7 @@ import kotlinx.atomicfu.atomic * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ -suspend fun awaitAll(vararg jobs: Job): Unit = jobs.asList().awaitAll() +public suspend fun awaitAll(vararg jobs: Job): Unit = jobs.asList().awaitAll() /** * Awaits for completion of given jobs without blocking a thread and resumes normally when all jobs computations are complete @@ -18,7 +18,7 @@ suspend fun awaitAll(vararg jobs: Job): Unit = jobs.asList().awaitAll() * If the [Job] of the current coroutine is l or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ -suspend fun Iterable.awaitAll(): Unit { +public suspend fun Iterable.awaitAll(): Unit { val jobs = this as? Collection ?: this.toList() if (jobs.isEmpty()) { return @@ -32,7 +32,7 @@ suspend fun Iterable.awaitAll(): Unit { * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ -suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() } +public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() } /** * Suspends current coroutine until all given jobs are complete. This method is semantically equivalent to @@ -40,7 +40,7 @@ suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() } * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ -suspend fun Iterable.joinAll(): Unit = forEach { it.join() } +public suspend fun Iterable.joinAll(): Unit = forEach { it.join() } private class AwaitAll(private val jobs: Collection) { private val notCompletedCount = atomic(jobs.size) diff --git a/knit/src/Knit.kt b/knit/src/Knit.kt index 566c80eea6..275fc286dd 100644 --- a/knit/src/Knit.kt +++ b/knit/src/Knit.kt @@ -410,8 +410,8 @@ fun writeLines(file: File, lines: List) { fun findModuleRootDir(name: String): String = moduleRoots - .map { it + "/" + name } - .firstOrNull { File(it + "/" + moduleMarker).exists() } + .map { "$it/$name" } + .firstOrNull { File("$it/$moduleMarker").exists() } ?: throw IllegalArgumentException("Module $name is not found in any of the module root dirs") data class ApiIndexKey( @@ -419,26 +419,21 @@ data class ApiIndexKey( val pkg: String ) -val apiIndexCache: MutableMap> = HashMap() +val apiIndexCache: MutableMap>> = HashMap() val REF_LINE_REGEX = Regex("([a-zA-z.]+)") val INDEX_HTML = "/index.html" val INDEX_MD = "/index.md" val FUNCTIONS_SECTION_HEADER = "### Functions" -val AMBIGUOUS = "#AMBIGUOUS: " - -fun HashMap.putUnambiguous(key: String, value: String) { +fun HashMap>.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) + if (oldValue != null) { + oldValue.add(value) + put(key, oldValue) + } else { + put(key, mutableListOf(value)) + } } fun loadApiIndex( @@ -446,10 +441,10 @@ fun loadApiIndex( path: String, pkg: String, namePrefix: String = "" -): Map? { +): Map>? { val fileName = docsRoot + "/" + path + INDEX_MD val visited = mutableSetOf() - val map = HashMap() + val map = HashMap>() var inFunctionsSection = false File(fileName).withLineNumberReader(::LineNumberReader) { while (true) { @@ -499,11 +494,12 @@ fun processApiIndex( 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 + if (refLink.size > 1) { + println("INFO: Ambiguous reference to [$refName]: $refLink, taking the shortest one") } - indexList += "[$refName]: $siteRoot/$refLink" + + val link = refLink.minBy { it.length } + indexList += "[$refName]: $siteRoot/$link" it.remove() } return indexList From f0ef14e0b6c4a48cc014d86d49433a47e75cdd40 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 11 Apr 2018 19:44:08 +0300 Subject: [PATCH 41/61] Start jobs during awaitAll call to properly handle lazily started coroutines --- .../coroutines/experimental/Deferred.kt | 2 +- .../kotlinx/coroutines/experimental/Await.kt | 1 + .../coroutines/experimental/AwaitTest.kt | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt index 3f15e8a580..7f23fbda35 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt @@ -152,7 +152,7 @@ public interface Deferred : Job { * Other options can be specified via `start` parameter. See [CoroutineStart] for details. * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,, * the resulting [Deferred] is created in _new_ state. It can be explicitly started with [start][Job.start] - * function and will be started implicitly on the first invocation of [join][Job.join] or [await][Deferred.await]. + * function and will be started implicitly on the first invocation of [join][Job.join], [await][Deferred.await] or [awaitAll]. * * @param context context of the coroutine. The default value is [DefaultDispatcher]. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt index 873b3b4dea..30d96d54c7 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -59,6 +59,7 @@ private class AwaitAll(private val jobs: Collection) { } jobs.forEach { + it.start() // To properly await lazily started jobs cont.disposeOnCompletion(it.invokeOnCompletion(handler)) } } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt index c87f0abf9b..14a6b54026 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt @@ -30,6 +30,15 @@ class AwaitTest : TestBase() { finish(6) } + @Test + fun testAwaitAllLazy() = runTest { + expect(1) + val d = async(coroutineContext, start = CoroutineStart.LAZY) { expect(2) } + val d2 = launch(coroutineContext, start = CoroutineStart.LAZY) { expect(3) } + awaitAll(d, d2) + finish(4) + } + @Test fun testAwaitAllTyped() = runTest { val d1 = async(coroutineContext) { 1L } @@ -244,6 +253,15 @@ class AwaitTest : TestBase() { finish(5) } + @Test + fun testJoinAllLazy() = runTest { + expect(1) + val d = async(coroutineContext, start = CoroutineStart.LAZY) { expect(2) } + val d2 = launch(coroutineContext, start = CoroutineStart.LAZY) { expect(3) } + joinAll(d, d2) + finish(4) + } + @Test fun testJoinAllExceptionally() = runTest { val d1 = launch(coroutineContext) { From 203abb0139435d387064547e495e6456ba9437d8 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Thu, 12 Apr 2018 12:34:14 +0300 Subject: [PATCH 42/61] awaitAll review and todos --- .../experimental/CompletedExceptionally.kt | 19 ++++++++++++------- .../kotlinx/coroutines/experimental/Await.kt | 15 ++++++--------- .../experimental/AwaitStressTest.kt | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt index 9e37586db9..07f1ffb47b 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt @@ -16,8 +16,6 @@ package kotlinx.coroutines.experimental -import kotlinx.coroutines.experimental.internalAnnotations.* - /** * Class for an internal state of a job that had completed exceptionally, including cancellation. * @@ -27,17 +25,22 @@ import kotlinx.coroutines.experimental.internalAnnotations.* * or artificial JobCancellationException if no cause was provided * @suppress **This is unstable API and it is subject to change.** */ -public open class CompletedExceptionally(public val cause: Throwable) { - +// todo: make it internal +public open class CompletedExceptionally( + public val cause: Throwable +) { /** * Returns completion exception. */ + @Deprecated("Use `cause`", replaceWith = ReplaceWith("cause")) + // todo: Remove exception usages public val exception: Throwable get() = cause // alias for backward compatibility /** * Returns true if the current exceptional reason is cancellation without cause */ - open fun isCancelledWithoutCause() = false + // todo: Remove it. Instead, classify based on `cause is CancellationException` + public open fun isCancelledWithoutCause() = false override fun toString(): String = "$classSimpleName[$exception]" } @@ -51,9 +54,11 @@ public open class CompletedExceptionally(public val cause: Throwable) { * @param cause the exceptional completion cause. If `cause` is null, then a [JobCancellationException] is created. * @suppress **This is unstable API and it is subject to change.** */ +// todo: make it internal public class Cancelled( - private val job: Job, - cause: Throwable?) : CompletedExceptionally(cause ?: JobCancellationException("Job was cancelled normally", null, job)) { + private val job: Job, + cause: Throwable? +) : CompletedExceptionally(cause ?: JobCancellationException("Job was cancelled normally", null, job)) { override fun isCancelledWithoutCause(): Boolean = cause is JobCancellationException && cause.job == job } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt index 30d96d54c7..be66375410 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -15,16 +15,12 @@ public suspend fun awaitAll(vararg jobs: Job): Unit = jobs.asList().awaitAll() * Awaits for completion of given jobs without blocking a thread and resumes normally when all jobs computations are complete * or resumes with the first thrown exception if any of computations completes exceptionally including cancellation. * This suspending function is cancellable. - * If the [Job] of the current coroutine is l or completed while this suspending function is waiting, + * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ -public suspend fun Iterable.awaitAll(): Unit { - val jobs = this as? Collection ?: this.toList() - if (jobs.isEmpty()) { - return - } - - AwaitAll(jobs).await() +public suspend fun Collection.awaitAll() { + if (isEmpty()) return + AwaitAll(this).await() } /** @@ -40,13 +36,14 @@ public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() } * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ -public suspend fun Iterable.joinAll(): Unit = forEach { it.join() } +public suspend fun Collection.joinAll(): Unit = forEach { it.join() } private class AwaitAll(private val jobs: Collection) { private val notCompletedCount = atomic(jobs.size) suspend fun await() { suspendCancellableCoroutine { cont -> + // todo: create a separate named class instance of JobNode to avoid extra object val handler: (Throwable?) -> Unit = { if (it != null) { val token = cont.tryResumeWithException(it) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt index 79869d7402..060b5dc473 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt @@ -10,7 +10,7 @@ class AwaitStressTest : TestBase() { override fun fillInStackTrace(): Throwable = this } - private val iterations = 50_000 + private val iterations = 50_000 * stressTestMultiplier private val pool = newFixedThreadPoolContext(4, "AwaitStressTest") @After From f5e63cab70a8c634fd81f3fc33661e8d3af0f9d2 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 12 Apr 2018 19:59:56 +0300 Subject: [PATCH 43/61] Replacing isCancelledWithoutCause with proper contract on exception, disabling stacktraces of JobCancellationException in production mode --- .../experimental/AbstractCoroutine.kt | 6 +-- .../experimental/CompletedExceptionally.kt | 21 +++------- .../kotlinx/coroutines/experimental/Job.kt | 42 ++++++++----------- .../experimental/CoroutineContext.kt | 2 +- .../coroutines/experimental/Exceptions.kt | 21 ++++++++-- .../experimental/channels/Produce.kt | 2 +- .../experimental/channels/ActorTest.kt | 38 ++++++++++++++--- .../experimental/channels/ArrayChannelTest.kt | 2 +- .../experimental/reactive/Publish.kt | 2 +- .../experimental/rx1/RxObservable.kt | 2 +- .../experimental/rx2/RxObservable.kt | 2 +- 11 files changed, 82 insertions(+), 58 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt index 5143c15a39..d5b3a5c85b 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt @@ -76,8 +76,8 @@ public abstract class AbstractCoroutine( * This function is invoked once when this coroutine is cancelled or is completed, * similarly to [invokeOnCompletion] with `onCancelling` set to `true`. * - * @param cause the cause that was passed to [Job.cancel] function or `null` if coroutine was cancelled - * without cause or is completing normally. + * @param cause the cause that was passed to [Job.cancel] function, [JobCancellationException] if it was completed without cause + * or `null` if coroutine is completing normally. */ protected open fun onCancellation(cause: Throwable?) {} @@ -98,7 +98,7 @@ public abstract class AbstractCoroutine( @Suppress("UNCHECKED_CAST") internal override fun onCompletionInternal(state: Any?, mode: Int) { if (state is CompletedExceptionally) - onCompletedExceptionally(state.exception) + onCompletedExceptionally(state.cause) else onCompleted(state as T) } diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt index 07f1ffb47b..d5ac1f453e 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt @@ -20,13 +20,13 @@ package kotlinx.coroutines.experimental * Class for an internal state of a job that had completed exceptionally, including cancellation. * * **Note: This class cannot be used outside of internal coroutines framework**. + * **Note: cannot be internal until we get rid of MutableDelegateContinuation in IO** * * @param cause the exceptional completion cause. It's either original exceptional cause * or artificial JobCancellationException if no cause was provided * @suppress **This is unstable API and it is subject to change.** */ -// todo: make it internal -public open class CompletedExceptionally( +open class CompletedExceptionally( public val cause: Throwable ) { /** @@ -36,13 +36,7 @@ public open class CompletedExceptionally( // todo: Remove exception usages public val exception: Throwable get() = cause // alias for backward compatibility - /** - * Returns true if the current exceptional reason is cancellation without cause - */ - // todo: Remove it. Instead, classify based on `cause is CancellationException` - public open fun isCancelledWithoutCause() = false - - override fun toString(): String = "$classSimpleName[$exception]" + override fun toString(): String = "$classSimpleName[$cause]" } /** @@ -54,12 +48,7 @@ public open class CompletedExceptionally( * @param cause the exceptional completion cause. If `cause` is null, then a [JobCancellationException] is created. * @suppress **This is unstable API and it is subject to change.** */ -// todo: make it internal -public class Cancelled( +internal class Cancelled( private val job: Job, cause: Throwable? -) : CompletedExceptionally(cause ?: JobCancellationException("Job was cancelled normally", null, job)) { - - override fun isCancelledWithoutCause(): Boolean = cause is JobCancellationException && cause.job == job -} - +) : CompletedExceptionally(cause ?: JobCancellationException("Job was cancelled normally", null, job)) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt index ca5b6d1b2b..de91824179 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt @@ -641,15 +641,16 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 private fun isCorrespondinglyCancelled(cancelled: Cancelled, proposedUpdate: Any?): Boolean { if (proposedUpdate !is Cancelled) return false // NOTE: equality comparison of causes is performed here by design, see equals of JobCancellationException - return proposedUpdate.cause == cancelled.cause || - proposedUpdate.cause is JobCancellationException && cancelled.isCancelledWithoutCause() + return proposedUpdate.cause == cancelled.cause || proposedUpdate.cause is JobCancellationException } private fun createCancelled(cancelled: Cancelled, proposedUpdate: Any?): Cancelled { if (proposedUpdate !is CompletedExceptionally) return cancelled // not exception -- just use original cancelled - val exception = proposedUpdate.exception - if (cancelled.exception == exception) return cancelled // that is the cancelled we need already! - if (!cancelled.isCancelledWithoutCause()) { + val exception = proposedUpdate.cause + if (cancelled.cause == exception) return cancelled // that is the cancelled we need already! + + // Do not spam with JCE in suppressed exceptions + if (cancelled.cause !is JobCancellationException) { exception.addSuppressedThrowable(cancelled.cause) } return Cancelled(this, exception) @@ -752,11 +753,11 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 val state = this.state return when { state is Finishing && state.cancelled != null -> - state.cancelled.exception.toCancellationException("Job is being cancelled") + state.cancelled.cause.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") + state.cause.toCancellationException("Job has failed") else -> JobCancellationException("Job has completed normally", null, this) } } @@ -766,21 +767,14 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 /** * 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 + * [cancel] cause, [JobCancellationException] or **`null` if this job had completed normally**. + * 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 -> { - if (state.cancelled.isCancelledWithoutCause()) { - null - } else { - state.cancelled.cause - } - } + 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 @@ -1060,7 +1054,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 } // cancel all children in list on exceptional completion if (proposedUpdate is CompletedExceptionally) - child?.cancelChildrenInternal(proposedUpdate.exception) + child?.cancelChildrenInternal(proposedUpdate.cause) // switch to completing state val cancelled = (state as? Finishing)?.cancelled ?: (proposedUpdate as? Cancelled) val completing = Finishing(list, cancelled, true) @@ -1080,7 +1074,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 } private val Any?.exceptionOrNull: Throwable? - get() = (this as? CompletedExceptionally)?.exception + get() = (this as? CompletedExceptionally)?.cause private fun firstChild(state: Incomplete) = state as? Child ?: state.list?.nextChild() @@ -1232,7 +1226,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 internal fun getCompletedInternal(): Any? { val state = this.state check(state !is Incomplete) { "This job has not completed yet" } - if (state is CompletedExceptionally) throw state.exception + if (state is CompletedExceptionally) throw state.cause return state } @@ -1245,7 +1239,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 val state = this.state if (state !is Incomplete) { // already complete -- just return result - if (state is CompletedExceptionally) throw state.exception + if (state is CompletedExceptionally) throw state.cause return state } @@ -1259,7 +1253,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 val state = this.state check(state !is Incomplete) if (state is CompletedExceptionally) - cont.resumeWithException(state.exception) + cont.resumeWithException(state.cause) else cont.resume(state) }) @@ -1278,7 +1272,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 // already complete -- select result if (select.trySelect(null)) { if (state is CompletedExceptionally) - select.resumeSelectCancellableWithException(state.exception) + select.resumeSelectCancellableWithException(state.cause) else block.startCoroutineUndispatched(state as T, select.completion) } @@ -1300,7 +1294,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 val state = this.state // Note: await is non-atomic (can be cancelled while dispatched) if (state is CompletedExceptionally) - select.resumeSelectCancellableWithException(state.exception) + select.resumeSelectCancellableWithException(state.cause) else block.startCoroutineCancellable(state as T, select.completion) } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt index 820d87a5da..d8eeedfb26 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt @@ -41,7 +41,7 @@ public const val DEBUG_PROPERTY_VALUE_ON = "on" */ public const val DEBUG_PROPERTY_VALUE_OFF = "off" -private val DEBUG = run { +internal val DEBUG = run { val value = try { System.getProperty(DEBUG_PROPERTY_NAME) } catch (e: SecurityException) { null } when (value) { diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt index 7d7c532af1..327d9406bd 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt @@ -16,8 +16,6 @@ package kotlinx.coroutines.experimental -import java.util.concurrent.* - /** * This exception gets thrown if an exception is caught while processing [CompletionHandler] invocation for [Job]. */ @@ -47,7 +45,24 @@ public actual class JobCancellationException public actual constructor( */ public actual val job: Job ) : CancellationException(message) { - init { if (cause != null) initCause(cause) } + + init { + if (cause != null) initCause(cause) + } + + override fun fillInStackTrace(): Throwable { + if (DEBUG) { + return super.fillInStackTrace() + } + + /* + * In non-debug mode we don't want to have a stacktrace on every cancellation/close, + * parent job reference is enough. Stacktrace of JCE is not needed most of the time (e.g., it is not logged) + * and hurts performance. + */ + return this + } + override fun toString(): String = "${super.toString()}; job=$job" override fun equals(other: Any?): Boolean = other === this || diff --git a/core/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 index d19c26e5f7..9578f221d0 100644 --- a/core/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 @@ -124,7 +124,7 @@ private class ProducerCoroutine( val cause = exceptionally?.cause val processed = when (exceptionally) { // producer coroutine was cancelled -- cancel channel, but without cause if it was closed without cause - is Cancelled -> _channel.cancel(if (exceptionally.isCancelledWithoutCause()) null else cause) + is Cancelled -> _channel.cancel(if (cause is JobCancellationException) null else cause) else -> _channel.close(cause) // producer coroutine has completed -- close channel } if (!processed && cause != null) diff --git a/core/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 index 3032944ab4..268ae31409 100644 --- a/core/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 @@ -20,9 +20,9 @@ import kotlinx.coroutines.experimental.* import org.hamcrest.core.* import org.junit.* import org.junit.Assert.* -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import java.io.IOException +import org.junit.runner.* +import org.junit.runners.* +import java.io.* import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) @@ -80,9 +80,8 @@ class ActorTest(private val capacity: Int) : TestBase() { finish(7) } - @Test - fun testCancelWithoutCause() = runTest { + fun testCloseWithoutCause() = runTest { val actor = actor(coroutineContext, capacity) { val element = channel.receiveOrNull() expect(2) @@ -101,7 +100,7 @@ class ActorTest(private val capacity: Int) : TestBase() { } @Test - fun testCancelWithCause() = runTest { + fun testCloseWithCause() = runTest { val actor = actor(coroutineContext, capacity) { val element = channel.receiveOrNull() expect(2) @@ -120,4 +119,31 @@ class ActorTest(private val capacity: Int) : TestBase() { yield() finish(4) } + + @Test + fun testCancelEnclosingJob() = runTest { + val job = async(coroutineContext) { + actor(coroutineContext, capacity) { + expect(1) + channel.receiveOrNull() + expectUnreached() + } + } + + yield() + yield() + + expect(2) + yield() + job.cancel() + + try { + job.await() + expectUnreached() + } catch (e: JobCancellationException) { + assertTrue(e.message?.contains("Job was cancelled normally") ?: false) + } + + finish(3) + } } diff --git a/core/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 index fa1b3c8884..fadf34071a 100644 --- a/core/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 @@ -19,7 +19,7 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* import org.junit.* import org.junit.Assert.* -import java.io.IOException +import java.io.* import kotlin.coroutines.experimental.* class ArrayChannelTest : TestBase() { 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 63fa447137..724b3b7fcc 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 @@ -167,7 +167,7 @@ private class PublisherCoroutine( _nRequested.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed) val cause = getCompletionCause() try { - if (cause != null) + if (cause != null && cause !is JobCancellationException) subscriber.onError(cause) else subscriber.onComplete() 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 e482de2330..38983254ee 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 @@ -168,7 +168,7 @@ private class RxObservableCoroutine( _nRequested.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed) val cause = getCompletionCause() try { - if (cause != null) + if (cause != null && cause !is JobCancellationException) subscriber.onError(cause) else subscriber.onCompleted() 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 02514957a5..8454e88034 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 @@ -158,7 +158,7 @@ private class RxObservableCoroutine( _signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed) val cause = getCompletionCause() try { - if (cause != null) + if (cause != null && cause !is JobCancellationException) subscriber.onError(cause) else subscriber.onComplete() From 05d382386517b459a8996c2bd3dfeff5a85b0642 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 12 Apr 2018 20:16:37 +0300 Subject: [PATCH 44/61] awaitAll improvements: create defensive copy of jobs to consistently handle concurrent mutations, make handler JobNode to avoid extra object allocation --- .../kotlinx/coroutines/experimental/Await.kt | 31 ++++++++-------- .../experimental/AwaitStressTest.kt | 36 +++++++++++++++++-- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt index be66375410..d759fe458e 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -20,7 +20,8 @@ public suspend fun awaitAll(vararg jobs: Job): Unit = jobs.asList().awaitAll() */ public suspend fun Collection.awaitAll() { if (isEmpty()) return - AwaitAll(this).await() + val snapshot = ArrayList(this) + AwaitAll(snapshot).await() } /** @@ -43,21 +44,23 @@ private class AwaitAll(private val jobs: Collection) { suspend fun await() { suspendCancellableCoroutine { cont -> - // todo: create a separate named class instance of JobNode to avoid extra object - val handler: (Throwable?) -> Unit = { - if (it != null) { - val token = cont.tryResumeWithException(it) - if (token != null) { - cont.completeResume(token) - } - } else if (notCompletedCount.decrementAndGet() == 0) { - cont.resume(Unit) - } - } - jobs.forEach { it.start() // To properly await lazily started jobs - cont.disposeOnCompletion(it.invokeOnCompletion(handler)) + cont.disposeOnCompletion(it.invokeOnCompletion(AwaitAllNode(cont, it))) + } + } + } + + inner class AwaitAllNode(private val continuation: CancellableContinuation, job: Job) : JobNode(job), CompletionHandler { + + override fun invoke(cause: Throwable?) { + if (cause != null) { + val token = continuation.tryResumeWithException(cause) + if (token != null) { + continuation.completeResume(token) + } + } else if (notCompletedCount.decrementAndGet() == 0) { + continuation.resume(Unit) } } } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt index 060b5dc473..df48ba2068 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt @@ -1,8 +1,8 @@ package kotlinx.coroutines.experimental -import org.junit.After -import java.util.concurrent.CyclicBarrier -import kotlin.test.Test +import org.junit.* +import org.junit.Test +import java.util.concurrent.* class AwaitStressTest : TestBase() { @@ -100,4 +100,34 @@ class AwaitStressTest : TestBase() { require(cancelledOnce) { "Cancellation exception wasn't properly caught" } } + + @Test + fun testMutatingCollection() = runTest { + val barrier = CyclicBarrier(4) + + repeat(iterations) { + val jobs = mutableListOf() + + jobs += async(pool) { + barrier.await() + 1L + } + + jobs += async(pool) { + barrier.await() + 2L + } + + jobs += async(pool) { + barrier.await() + jobs.removeAt(2) + } + + val allJobs = ArrayList(jobs) + barrier.await() + jobs.awaitAll() // shouldn't hang + allJobs.awaitAll() + barrier.reset() + } + } } From c1092d53c93576d90007f40a40bb087ee8930f02 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 12 Apr 2018 20:22:25 +0300 Subject: [PATCH 45/61] Remove CompletedExceptionally#exception --- .../coroutines/experimental/AbstractContinuation.kt | 6 +++--- .../coroutines/experimental/Builders.common.kt | 2 +- .../coroutines/experimental/CompletedExceptionally.kt | 11 +---------- .../kotlinx/coroutines/experimental/Dispatched.kt | 2 +- .../kotlinx/coroutines/experimental/Scheduled.kt | 4 ++-- .../experimental/intrinsics/Undispatched.kt | 2 +- .../kotlinx/coroutines/experimental/Builders.kt | 2 +- 7 files changed, 10 insertions(+), 19 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt index 67a7fc64e7..707d6c2e53 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt @@ -75,7 +75,7 @@ internal abstract class AbstractContinuation( if (trySuspend()) return COROUTINE_SUSPENDED // otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state val state = this.state - if (state is CompletedExceptionally) throw state.exception + if (state is CompletedExceptionally) throw state.cause return getSuccessfulResult(state) } @@ -99,8 +99,8 @@ internal abstract class AbstractContinuation( } is Cancelled -> { // Ignore resumes in cancelled coroutines, but handle exception if a different one here - if (proposedUpdate is CompletedExceptionally && proposedUpdate.exception != state.exception) - handleException(proposedUpdate.exception) + if (proposedUpdate is CompletedExceptionally && proposedUpdate.cause != state.cause) + handleException(proposedUpdate.cause) return } else -> error("Already resumed, but got $proposedUpdate") diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt index 5c6d1f20c1..63f7fe28e5 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Builders.common.kt @@ -171,7 +171,7 @@ private open class StandaloneCoroutine( override fun hasOnFinishingHandler(update: Any?) = update is CompletedExceptionally override fun onFinishingInternal(update: Any?) { // note the use of the parent's job context below! - if (update is CompletedExceptionally) handleCoroutineException(parentContext, update.exception) + if (update is CompletedExceptionally) handleCoroutineException(parentContext, update.cause) } } diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt index d5ac1f453e..9940dfcd74 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt @@ -26,16 +26,7 @@ package kotlinx.coroutines.experimental * or artificial JobCancellationException if no cause was provided * @suppress **This is unstable API and it is subject to change.** */ -open class CompletedExceptionally( - public val cause: Throwable -) { - /** - * Returns completion exception. - */ - @Deprecated("Use `cause`", replaceWith = ReplaceWith("cause")) - // todo: Remove exception usages - public val exception: Throwable get() = cause // alias for backward compatibility - +open class CompletedExceptionally(public val cause: Throwable) { override fun toString(): String = "$classSimpleName[$cause]" } diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Dispatched.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Dispatched.kt index e81e5a920d..5bd1668db9 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Dispatched.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Dispatched.kt @@ -142,7 +142,7 @@ public interface DispatchedTask : Runnable { state as T public fun getExceptionalResult(state: Any?): Throwable? = - (state as? CompletedExceptionally)?.exception + (state as? CompletedExceptionally)?.cause public override fun run() { try { diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt index e06323d105..4c1ce77410 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt @@ -102,7 +102,7 @@ private open class TimeoutCoroutine( @Suppress("UNCHECKED_CAST") internal override fun onCompletionInternal(state: Any?, mode: Int) { if (state is CompletedExceptionally) - cont.resumeWithExceptionMode(state.exception, mode) + cont.resumeWithExceptionMode(state.cause, mode) else cont.resumeMode(state as T, mode) } @@ -171,7 +171,7 @@ private class TimeoutOrNullCoroutine( @Suppress("UNCHECKED_CAST") internal override fun onCompletionInternal(state: Any?, mode: Int) { if (state is CompletedExceptionally) { - val exception = state.exception + val exception = state.cause if (exception is TimeoutCancellationException && exception.coroutine === this) cont.resumeMode(null, mode) else cont.resumeWithExceptionMode(exception, mode) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/intrinsics/Undispatched.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/intrinsics/Undispatched.kt index fc8783a86c..8178d8733e 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/intrinsics/Undispatched.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/intrinsics/Undispatched.kt @@ -85,7 +85,7 @@ private inline fun AbstractCoroutine.undispatchedResult(startBlock: () -> return when { result === COROUTINE_SUSPENDED -> COROUTINE_SUSPENDED makeCompletingOnce(result, MODE_IGNORE) -> { - if (result is CompletedExceptionally) throw result.exception else result + if (result is CompletedExceptionally) throw result.cause else result } else -> COROUTINE_SUSPENDED } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt index 7d20709f4e..d4ef498589 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt @@ -94,7 +94,7 @@ private class BlockingCoroutine( timeSource.unregisterTimeLoopThread() // now return result val state = this.state - (state as? CompletedExceptionally)?.let { throw it.exception } + (state as? CompletedExceptionally)?.let { throw it.cause } return state as T } } From e89cd688036b5dcbf74921275903585c5f1f1c7d Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 25 Apr 2018 13:03:40 +0300 Subject: [PATCH 46/61] More comments about CancellationException and its consistent use; JobCancellationException.job is made internal. --- .../coroutines/experimental/AbstractCoroutine.kt | 7 +++++-- .../experimental/CompletedExceptionally.kt | 6 +++++- .../experimental/CompletionHandler.common.kt | 6 ++++++ .../coroutines/experimental/Exceptions.common.kt | 2 +- .../kotlin/kotlinx/coroutines/experimental/Job.kt | 15 ++++++++++++++- .../kotlinx/coroutines/experimental/Exceptions.kt | 5 +---- .../coroutines/experimental/channels/Produce.kt | 2 +- .../kotlinx/coroutines/experimental/Exceptions.kt | 5 +---- .../coroutines/experimental/reactive/Publish.kt | 2 +- .../coroutines/experimental/rx1/RxObservable.kt | 2 +- .../coroutines/experimental/rx2/RxObservable.kt | 2 +- 11 files changed, 37 insertions(+), 17 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt index d5b3a5c85b..2078a3cc1e 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractCoroutine.kt @@ -76,8 +76,11 @@ public abstract class AbstractCoroutine( * This function is invoked once when this coroutine is cancelled or is completed, * similarly to [invokeOnCompletion] with `onCancelling` set to `true`. * - * @param cause the cause that was passed to [Job.cancel] function, [JobCancellationException] if it was completed without cause - * or `null` if coroutine is completing normally. + * The meaning of [cause] parameter: + * * Cause is `null` when job has completed normally. + * * Cause is an instance of [CancellationException] when job was cancelled _normally_. + * **It should not be treated as an error**. In particular, it should not be reported to error logs. + * * Otherwise, the job had _failed_. */ protected open fun onCancellation(cause: Throwable?) {} diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt index 9940dfcd74..31c5c6dfa3 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletedExceptionally.kt @@ -16,6 +16,8 @@ package kotlinx.coroutines.experimental +import kotlinx.coroutines.experimental.internalAnnotations.* + /** * Class for an internal state of a job that had completed exceptionally, including cancellation. * @@ -26,7 +28,9 @@ package kotlinx.coroutines.experimental * or artificial JobCancellationException if no cause was provided * @suppress **This is unstable API and it is subject to change.** */ -open class CompletedExceptionally(public val cause: Throwable) { +open class CompletedExceptionally( + @JvmField public val cause: Throwable +) { override fun toString(): String = "$classSimpleName[$cause]" } diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt index dc2fd9ae31..e398fae220 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt @@ -24,6 +24,12 @@ import kotlinx.coroutines.experimental.internal.* * Installed handler should not throw any exceptions. If it does, they will get caught, * wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code. * + * The meaning of `cause` that is passed to the handler: + * * Cause is `null` when job has completed normally. + * * Cause is an instance of [CancellationException] when job was cancelled _normally_. + * **It should not be treated as an error**. In particular, it should not be reported to error logs. + * * Otherwise, the job had _failed_. + * * **Note**: This type is a part of internal machinery that supports parent-child hierarchies * and allows for implementation of suspending functions that wait on the Job's state. * This type should not be used in general application code. diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt index e2e67d38ab..c12501e641 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt @@ -25,7 +25,7 @@ public expect class JobCancellationException( cause: Throwable?, job: Job ) : CancellationException { - val job: Job + internal val job: Job } internal expect class DispatchException(message: String, cause: Throwable) : RuntimeException diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt index de91824179..3642688a08 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt @@ -279,6 +279,12 @@ public interface Job : CoroutineContext.Element { * with a job's exception or cancellation cause or `null`. Otherwise, handler will be invoked once when this * job is complete. * + * The meaning of `cause` that is passed to the handler: + * * Cause is `null` when job has completed normally. + * * Cause is an instance of [CancellationException] when job was cancelled _normally_. + * **It should not be treated as an error**. In particular, it should not be reported to error logs. + * * Otherwise, the job had _failed_. + * * The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] the * registration of this handler and release its memory if its invocation is no longer needed. * There is no need to dispose the handler after completion of this job. The references to @@ -297,6 +303,12 @@ public interface Job : CoroutineContext.Element { * with a job's cancellation cause or `null` unless [invokeImmediately] is set to false. * Otherwise, handler will be invoked once when this job is cancelled or complete. * + * The meaning of `cause` that is passed to the handler: + * * Cause is `null` when job has completed normally. + * * Cause is an instance of [CancellationException] when job was cancelled _normally_. + * **It should not be treated as an error**. In particular, it should not be reported to error logs. + * * Otherwise, the job had _failed_. + * * Invocation of this handler on a transition to a transient _cancelling_ state * is controlled by [onCancelling] boolean parameter. * The handler is invoked on invocation of [cancel] when @@ -648,7 +660,8 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0 if (proposedUpdate !is CompletedExceptionally) return cancelled // not exception -- just use original cancelled val exception = proposedUpdate.cause if (cancelled.cause == exception) return cancelled // that is the cancelled we need already! - + // todo: We need to rework this logic to keep original cancellation cause in the state and suppress other exceptions + // that could have occurred while coroutine is being cancelled. // Do not spam with JCE in suppressed exceptions if (cancelled.cause !is JobCancellationException) { exception.addSuppressedThrowable(cancelled.cause) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt index 327d9406bd..462d816d78 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt @@ -40,10 +40,7 @@ public actual typealias CancellationException = java.util.concurrent.Cancellatio public actual class JobCancellationException public actual constructor( message: String, cause: Throwable?, - /** - * The job that was cancelled. - */ - public actual val job: Job + @JvmField internal actual val job: Job ) : CancellationException(message) { init { diff --git a/core/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 index 9578f221d0..3c32e145dd 100644 --- a/core/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 @@ -124,7 +124,7 @@ private class ProducerCoroutine( val cause = exceptionally?.cause val processed = when (exceptionally) { // producer coroutine was cancelled -- cancel channel, but without cause if it was closed without cause - is Cancelled -> _channel.cancel(if (cause is JobCancellationException) null else cause) + is Cancelled -> _channel.cancel(if (cause is CancellationException) null else cause) else -> _channel.close(cause) // producer coroutine has completed -- close channel } if (!processed && cause != null) diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt index 37a04377aa..0220b2d618 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt @@ -40,10 +40,7 @@ public actual open class CancellationException actual constructor(message: Strin public actual class JobCancellationException public actual constructor( message: String, public override val cause: Throwable?, - /** - * The job that was cancelled. - */ - public actual val job: Job + internal actual val job: Job ) : CancellationException(message.withCause(cause)) { override fun toString(): String = "${super.toString()}; job=$job" override fun equals(other: Any?): Boolean = 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 724b3b7fcc..ba53496047 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 @@ -167,7 +167,7 @@ private class PublisherCoroutine( _nRequested.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed) val cause = getCompletionCause() try { - if (cause != null && cause !is JobCancellationException) + if (cause != null && cause !is CancellationException) subscriber.onError(cause) else subscriber.onComplete() 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 38983254ee..f1fd15a407 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 @@ -168,7 +168,7 @@ private class RxObservableCoroutine( _nRequested.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed) val cause = getCompletionCause() try { - if (cause != null && cause !is JobCancellationException) + if (cause != null && cause !is CancellationException) subscriber.onError(cause) else subscriber.onCompleted() 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 8454e88034..55f1e36115 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 @@ -158,7 +158,7 @@ private class RxObservableCoroutine( _signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed) val cause = getCompletionCause() try { - if (cause != null && cause !is JobCancellationException) + if (cause != null && cause !is CancellationException) subscriber.onError(cause) else subscriber.onComplete() From 189e995c594e32f6781b14cec1c5ed8f41071c5a Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Thu, 26 Apr 2018 11:06:37 +0300 Subject: [PATCH 47/61] awaitAll should be defined only for Deferred types and return results list --- .../kotlinx/coroutines/experimental/Await.kt | 56 +++++++++++-------- .../experimental/AwaitStressTest.kt | 18 +++--- .../coroutines/experimental/AwaitTest.kt | 32 +++++------ 3 files changed, 59 insertions(+), 47 deletions(-) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt index d759fe458e..d39d300bd9 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -3,56 +3,64 @@ package kotlinx.coroutines.experimental import kotlinx.atomicfu.atomic /** - * Awaits for completion of given jobs without blocking a thread and resumes normally when all deferred computations are complete - * or resumes with the first thrown exception if any of computations completes exceptionally including cancellation. + * Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values + * when all deferred computations are complete or resumes with the first thrown exception if any of computations + * complete exceptionally including cancellation. + * + * This function is **not** equivalent to `deferreds.map { it.await() }` which fails only when when it sequentially + * gets to wait the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail. + * * This suspending function is cancellable. * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ -public suspend fun awaitAll(vararg jobs: Job): Unit = jobs.asList().awaitAll() +public suspend fun awaitAll(vararg deferreds: Deferred): List = + if (deferreds.isEmpty()) emptyList() else AwaitAll(deferreds).await() /** - * Awaits for completion of given jobs without blocking a thread and resumes normally when all jobs computations are complete - * or resumes with the first thrown exception if any of computations completes exceptionally including cancellation. + * Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values + * when all deferred computations are complete or resumes with the first thrown exception if any of computations + * complete exceptionally including cancellation. + * + * This function is **not** equivalent to `this.map { it.await() }` which fails only when when it sequentially + * gets to wait the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail. + * * This suspending function is cancellable. * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ -public suspend fun Collection.awaitAll() { - if (isEmpty()) return - val snapshot = ArrayList(this) - AwaitAll(snapshot).await() -} +public suspend fun Collection>.awaitAll(): List = + if (isEmpty()) emptyList() else AwaitAll(toTypedArray()).await() /** - * Suspends current coroutine until all given jobs are complete. This method is semantically equivalent to joining all given jobs one by one. + * Suspends current coroutine until all given jobs are complete. + * This method is semantically equivalent to joining all given jobs one by one with `jobs.forEach { it.join() }`. + * * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() } /** - * Suspends current coroutine until all given jobs are complete. This method is semantically equivalent to - * joining all given jobs one by one. + * Suspends current coroutine until all given jobs are complete. + * This method is semantically equivalent to joining all given jobs one by one with `forEach { it.join() }`. + * * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, * this function immediately resumes with [CancellationException]. */ public suspend fun Collection.joinAll(): Unit = forEach { it.join() } -private class AwaitAll(private val jobs: Collection) { - private val notCompletedCount = atomic(jobs.size) +private class AwaitAll(private val deferreds: Array>) { + private val notCompletedCount = atomic(deferreds.size) - suspend fun await() { - suspendCancellableCoroutine { cont -> - jobs.forEach { - it.start() // To properly await lazily started jobs - cont.disposeOnCompletion(it.invokeOnCompletion(AwaitAllNode(cont, it))) - } + suspend fun await(): List = suspendCancellableCoroutine { cont -> + deferreds.forEach { + it.start() // To properly await lazily started deferreds + cont.disposeOnCompletion(it.invokeOnCompletion(AwaitAllNode(cont, it))) } } - inner class AwaitAllNode(private val continuation: CancellableContinuation, job: Job) : JobNode(job), CompletionHandler { - + inner class AwaitAllNode(private val continuation: CancellableContinuation>, job: Job) : JobNode(job), CompletionHandler { override fun invoke(cause: Throwable?) { if (cause != null) { val token = continuation.tryResumeWithException(cause) @@ -60,7 +68,7 @@ private class AwaitAll(private val jobs: Collection) { continuation.completeResume(token) } } else if (notCompletedCount.decrementAndGet() == 0) { - continuation.resume(Unit) + continuation.resume(deferreds.map { it.getCompleted() }) } } } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt index df48ba2068..95e6bf8c63 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt @@ -3,6 +3,7 @@ package kotlinx.coroutines.experimental import org.junit.* import org.junit.Test import java.util.concurrent.* +import kotlin.test.* class AwaitStressTest : TestBase() { @@ -106,26 +107,29 @@ class AwaitStressTest : TestBase() { val barrier = CyclicBarrier(4) repeat(iterations) { - val jobs = mutableListOf() + // thread-safe collection that we are going to modify + val deferreds = CopyOnWriteArrayList>() - jobs += async(pool) { + deferreds += async(pool) { barrier.await() 1L } - jobs += async(pool) { + deferreds += async(pool) { barrier.await() 2L } - jobs += async(pool) { + deferreds += async(pool) { barrier.await() - jobs.removeAt(2) + deferreds.removeAt(2) + 3L } - val allJobs = ArrayList(jobs) + val allJobs = ArrayList(deferreds) barrier.await() - jobs.awaitAll() // shouldn't hang + val results = deferreds.awaitAll() // shouldn't hang + check(results == listOf(1L, 2L, 3L) || results == listOf(1L, 2L)) allJobs.awaitAll() barrier.reset() } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt index 14a6b54026..873b4080f2 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt @@ -1,7 +1,7 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.coroutineContext -import kotlin.test.Test +import kotlin.test.* class AwaitTest : TestBase() { @@ -22,7 +22,7 @@ class AwaitTest : TestBase() { expect(2) require(d2.isActive && !d2.isCompleted) - awaitAll(d, d2) + assertEquals(listOf("OK", 1L), awaitAll(d, d2)) expect(5) require(d.isCompleted && d2.isCompleted) @@ -33,9 +33,9 @@ class AwaitTest : TestBase() { @Test fun testAwaitAllLazy() = runTest { expect(1) - val d = async(coroutineContext, start = CoroutineStart.LAZY) { expect(2) } - val d2 = launch(coroutineContext, start = CoroutineStart.LAZY) { expect(3) } - awaitAll(d, d2) + val d = async(coroutineContext, start = CoroutineStart.LAZY) { expect(2); 1 } + val d2 = async(coroutineContext, start = CoroutineStart.LAZY) { expect(3); 2 } + assertEquals(listOf(1, 2), awaitAll(d, d2)) finish(4) } @@ -43,11 +43,11 @@ class AwaitTest : TestBase() { fun testAwaitAllTyped() = runTest { val d1 = async(coroutineContext) { 1L } val d2 = async(coroutineContext) { "" } - val d3 = launch(coroutineContext) { } + val d3 = async(coroutineContext) { } - setOf(d1, d2).awaitAll() - setOf(d1, d3).awaitAll() - listOf(d2, d3).awaitAll() + assertEquals(listOf(1L, ""), listOf(d1, d2).awaitAll()) + assertEquals(listOf(1L, Unit), listOf(d1, d3).awaitAll()) + assertEquals(listOf("", Unit), listOf(d2, d3).awaitAll()) } @Test @@ -134,11 +134,11 @@ class AwaitTest : TestBase() { @Test fun testAwaitAllPartiallyCompleted() = runTest { - val d1 = async(coroutineContext) { expect(1) } + val d1 = async(coroutineContext) { expect(1); 1 } d1.await() - val d2 = launch(coroutineContext) { expect(3) } + val d2 = async(coroutineContext) { expect(3); 2 } expect(2) - awaitAll(d1, d2) + assertEquals(listOf(1, 2), awaitAll(d1, d2)) require(d1.isCompleted && d2.isCompleted) finish(4) } @@ -209,9 +209,9 @@ class AwaitTest : TestBase() { @Test fun testAwaitAllSameJobMultipleTimes() = runTest { - val job = launch(coroutineContext) { } + val d = async(coroutineContext) { "OK" } // Duplicates are allowed though kdoc doesn't guarantee that - awaitAll(job, job, job) + assertEquals(listOf("OK", "OK", "OK"), awaitAll(d, d, d)) } @Test @@ -232,8 +232,8 @@ class AwaitTest : TestBase() { @Test fun testAwaitAllEmpty() = runTest { expect(1) - awaitAll() - emptyList().awaitAll() + assertEquals(emptyList(), awaitAll()) + assertEquals(emptyList(), emptyList>().awaitAll()) finish(2) } From 961913428b40d1abbdfda285373eb7bca20277b0 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 20 Apr 2018 18:13:33 +0300 Subject: [PATCH 48/61] Migrate channels and related operators to common, so channels can be used from JS Fixes #201 --- .../experimental/Exceptions.common.kt | 2 +- .../experimental/channels/AbstractChannel.kt | 1 + .../channels/ArrayBroadcastChannel.kt | 11 +- .../experimental/channels/ArrayChannel.kt | 6 +- .../experimental/channels/BroadcastChannel.kt | 3 +- .../experimental/channels/Channel.kt | 0 .../experimental/channels/ChannelCoroutine.kt | 7 + .../experimental/channels/Channels.kt | 20 +- .../channels/ConflatedBroadcastChannel.kt | 15 +- .../experimental/channels/ConflatedChannel.kt | 1 - .../channels/LinkedListChannel.kt | 0 .../experimental/channels/Produce.kt | 0 .../channels/RendezvousChannel.kt | 0 .../experimental/internal/ArrayCopy.common.kt | 6 + .../experimental/internal/Closeable.common.kt | 5 + .../internal/Concurrent.common.kt | 18 ++ .../internal/LockFreeLinkedList.common.kt | 18 +- .../channels/ArrayBroadcastChannelTest.kt | 62 +++--- .../experimental/channels/ArrayChannelTest.kt | 37 +--- .../channels/BroadcastChannelFactoryTest.kt | 26 +-- .../channels/ChannelFactoryTest.kt | 27 +-- .../experimental/channels/ChannelsTest.kt | 194 ++++++++---------- .../channels/ConflatedBroadcastChannelTest.kt | 70 ++++--- .../channels/ConflatedChannelTest.kt | 35 ++-- .../channels/LinkedListChannelTest.kt | 21 +- .../channels/ProduceConsumeTest.kt | 55 +++++ .../experimental/channels/ProduceTest.kt | 5 +- .../channels/RendezvousChannelTest.kt | 53 ++--- .../channels/SendReceiveStressTest.kt | 46 +++++ .../channels/SimpleSendReceiveTest.kt | 35 ++++ .../channels/TestBroadcastChannelKind.kt | 6 +- .../experimental/channels/TestChannelKind.kt | 18 +- .../experimental/channels/ChannelsJvm.kt | 22 ++ .../experimental/internal/ArrayCopy.kt | 5 + .../experimental/internal/Closeable.kt | 3 + .../experimental/internal/Concurrent.kt | 10 + .../internal/LockFreeLinkedList.kt | 8 +- .../channels/ArrayChannelStressTest.kt | 39 ++++ .../experimental/channels/ChannelsJvmTest.kt | 21 ++ .../channels/DoubleChannelCloseStressTest.kt | 2 +- ...onsumeTest.kt => ProduceConsumeJvmTest.kt} | 6 +- .../channels/RandevouzChannelStressTest.kt | 26 +++ .../channels/SendReceiveJvmStressTest.kt | 45 ++++ ...iveTest.kt => SimpleSendReceiveJvmTest.kt} | 4 +- .../coroutines/experimental/Exceptions.kt | 2 +- .../experimental/internal/ArrayCopy.kt | 9 + .../experimental/internal/Closeable.kt | 5 + .../experimental/internal/Concurrent.kt | 12 ++ .../experimental/internal/LinkedList.kt | 24 +++ .../experimental/internal/ArrayCopyKtTest.kt | 14 ++ 50 files changed, 711 insertions(+), 349 deletions(-) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt (99%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt (98%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt (98%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt (97%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt (100%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt (82%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt (98%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt (95%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt (99%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt (100%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt (100%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt (100%) create mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.common.kt create mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.common.kt create mode 100644 common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.common.kt rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt (72%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt (85%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt (58%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt (54%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsTest.kt (77%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt (58%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt (71%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt (72%) create mode 100644 common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt (98%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt (85%) create mode 100644 common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/SendReceiveStressTest.kt create mode 100644 common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestBroadcastChannelKind.kt (95%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt (82%) create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelsJvm.kt create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelStressTest.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsJvmTest.kt rename core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/{ProduceConsumeTest.kt => ProduceConsumeJvmTest.kt} (96%) create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RandevouzChannelStressTest.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SendReceiveJvmStressTest.kt rename core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/{SimpleSendReceiveTest.kt => SimpleSendReceiveJvmTest.kt} (98%) create mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt create mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt create mode 100644 js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt create mode 100644 js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopyKtTest.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt index e2e67d38ab..6759e8f845 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.common.kt @@ -18,7 +18,7 @@ package kotlinx.coroutines.experimental public expect class CompletionHandlerException(message: String, cause: Throwable) : RuntimeException -public expect open class CancellationException(message: String) : IllegalStateException +public expect open class CancellationException(message: String?) : IllegalStateException public expect class JobCancellationException( message: String, diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt similarity index 99% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt index 4ce28dad7b..5fcfc87bf2 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt @@ -18,6 +18,7 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.* import kotlinx.coroutines.experimental.intrinsics.* import kotlinx.coroutines.experimental.selects.* import kotlin.coroutines.experimental.* diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt similarity index 98% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt index 5c4fa55288..ffdf694dca 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt @@ -16,10 +16,9 @@ package kotlinx.coroutines.experimental.channels +import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.* import kotlinx.coroutines.experimental.selects.* -import java.util.concurrent.* -import java.util.concurrent.locks.* -import kotlin.concurrent.* /** * Broadcast channel with array buffer of a fixed [capacity]. @@ -64,7 +63,7 @@ class ArrayBroadcastChannel( So read/writes to buffer need not be volatile */ - private val subs = CopyOnWriteArrayList>() + private val subs = subscriberList>() override val isBufferAlwaysFull: Boolean get() = false override val isBufferFull: Boolean get() = size >= capacity @@ -132,7 +131,6 @@ class ArrayBroadcastChannel( // updates head if needed and optionally adds / removes subscriber under the same lock private tailrec fun updateHead(addSub: Subscriber? = null, removeSub: Subscriber? = null) { - assert(addSub == null || removeSub == null) // only one of them can be specified // update head in a tail rec loop var send: Send? = null var token: Any? = null @@ -200,7 +198,8 @@ class ArrayBroadcastChannel( ) : AbstractChannel(), SubscriptionReceiveChannel { private val subLock = ReentrantLock() - @Volatile @JvmField + @Volatile + @JvmField var subHead: Long = 0 // guarded by subLock override val isBufferAlwaysEmpty: Boolean get() = false diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt similarity index 98% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt index fd8c79d5d1..b118a896c8 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt @@ -16,9 +16,9 @@ package kotlinx.coroutines.experimental.channels +import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.Volatile import kotlinx.coroutines.experimental.selects.* -import java.util.concurrent.locks.* -import kotlin.concurrent.* /** * Channel with array buffer of a fixed [capacity]. @@ -249,4 +249,4 @@ public open class ArrayChannel( override val bufferDebugString: String get() = "(buffer:capacity=${buffer.size},size=$size)" -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt similarity index 97% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt index e071aa216d..0a9c589bdf 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt @@ -16,9 +16,10 @@ package kotlinx.coroutines.experimental.channels +import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.Channel.Factory.CONFLATED import kotlinx.coroutines.experimental.channels.Channel.Factory.UNLIMITED -import java.io.Closeable +import kotlinx.coroutines.experimental.internal.Closeable /** * Broadcast channel is a non-blocking primitive for communication between the sender and multiple receivers diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt similarity index 82% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt index a527be2387..a23c2dc79d 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt @@ -27,5 +27,12 @@ internal open class ChannelCoroutine( val channel: Channel get() = this + // Workaround for KT-23094 + override suspend fun receive(): E = _channel.receive() + + override suspend fun send(element: E) = _channel.send(element) + + override suspend fun receiveOrNull(): E? = _channel.receiveOrNull() + override fun cancel(cause: Throwable?): Boolean = super.cancel(cause) } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt similarity index 98% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt index 87abbf567c..ecb4262622 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channels.kt @@ -21,24 +21,6 @@ import kotlin.coroutines.experimental.* internal const val DEFAULT_CLOSE_MESSAGE = "Channel was closed" -// -------- Operations on SendChannel -------- - -/** - * Adds [element] into to this channel, **blocking** the caller while this channel [Channel.isFull], - * or throws exception if the channel [Channel.isClosedForSend] (see [Channel.close] for details). - * - * This is a way to call [Channel.send] method inside a blocking code using [runBlocking], - * so this function should not be used from coroutine. - */ -public fun SendChannel.sendBlocking(element: E) { - // fast path - if (offer(element)) - return - // slow path - runBlocking { - send(element) - } -} // -------- Conversions to ReceiveChannel -------- @@ -120,7 +102,7 @@ public fun consumesAll(vararg channels: ReceiveChannel<*>): CompletionHandler = if (exception == null) { exception = e } else { - exception.addSuppressed(e) + exception.addSuppressedThrowable(e) } } exception?.let { throw it } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt similarity index 95% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt index 95ae09037b..a8ec1ff9fe 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannel.kt @@ -16,12 +16,11 @@ package kotlinx.coroutines.experimental.channels -import kotlinx.atomicfu.atomic -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 +import kotlinx.atomicfu.* +import kotlinx.coroutines.experimental.internal.* +import kotlinx.coroutines.experimental.internalAnnotations.* +import kotlinx.coroutines.experimental.intrinsics.* +import kotlinx.coroutines.experimental.selects.* /** * Broadcasts the most recently sent element (aka [value]) to all [openSubscription] subscribers. @@ -162,8 +161,8 @@ public class ConflatedBroadcastChannel() : BroadcastChannel { check(i >= 0) if (n == 1) return null val update = arrayOfNulls>(n - 1) - System.arraycopy(list, 0, update, 0, i) - System.arraycopy(list, i + 1, update, i, n - i - 1) + arraycopy(list, 0, update, 0, i) + arraycopy(list, i + 1, update, i, n - i - 1) return update as Array> } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt similarity index 99% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt index 7589179c0a..ae9875670a 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannel.kt @@ -80,4 +80,3 @@ public open class ConflatedChannel : AbstractChannel() { } } } - diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannel.kt diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannel.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.common.kt new file mode 100644 index 0000000000..fe20163456 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.common.kt @@ -0,0 +1,6 @@ +package kotlinx.coroutines.experimental.internal + +/** + * Cross-platform array copy. Overlaps of source and destination are not supported + */ +expect fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int) \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.common.kt new file mode 100644 index 0000000000..2b1f504b2b --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.common.kt @@ -0,0 +1,5 @@ +package kotlinx.coroutines.experimental.internal + +expect interface Closeable { + fun close() +} diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.common.kt new file mode 100644 index 0000000000..df36305a29 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.common.kt @@ -0,0 +1,18 @@ +package kotlinx.coroutines.experimental.internal + +/** + * Special kind of list intended to be used as collection of subscribers in [ArrayBroadcastChannel] + * On JVM it's CopyOnWriteList and on JS it's MutableList. + * + * Note that this alias is intentionally not named as CopyOnWriteList to avoid accidental misusage outside of ArrayBroadcastChannel + */ +typealias SubscribersList = MutableList + +expect fun subscriberList(): SubscribersList + +expect class ReentrantLock() { + fun tryLock(): Boolean + fun unlock(): Unit +} + +expect inline fun ReentrantLock.withLock(action: () -> T): T diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt index 9cc3e141c9..e23fd988db 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt @@ -16,12 +16,12 @@ package kotlinx.coroutines.experimental.internal -import kotlin.jvm.* - /** @suppress **This is unstable API and it is subject to change.** */ public expect open class LockFreeLinkedListNode() { public val isRemoved: Boolean + public val next: Any public val nextNode: LockFreeLinkedListNode + public val prev: Any public val prevNode: LockFreeLinkedListNode public fun addLast(node: LockFreeLinkedListNode) public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean @@ -57,11 +57,23 @@ public expect open class AddLastDesc( val queue: LockFreeLinkedListNode val node: T protected override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? + override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) +} + +public expect open class RemoveFirstDesc(queue: LockFreeLinkedListNode): AbstractAtomicDesc { + val queue: LockFreeLinkedListNode + public val result: T + protected open fun validatePrepared(node: T): Boolean + protected final override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? + final override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) } /** @suppress **This is unstable API and it is subject to change.** */ public expect abstract class AbstractAtomicDesc : AtomicDesc { - protected abstract fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? final override fun prepare(op: AtomicOp<*>): Any? final override fun complete(op: AtomicOp<*>, failure: Any?) + protected open fun failure(affected: LockFreeLinkedListNode, next: Any): Any? + protected open fun retry(affected: LockFreeLinkedListNode, next: Any): Boolean + protected abstract fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? // non-null on failure + protected abstract fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt similarity index 72% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt index a71e3f8a56..84da40fe43 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannelTest.kt @@ -17,28 +17,27 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.core.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class ArrayBroadcastChannelTest : TestBase() { + @Test - fun testBasic() = runBlocking { + fun testBasic() = runTest { expect(1) val broadcast = ArrayBroadcastChannel(1) - assertThat(broadcast.isClosedForSend, IsEqual(false)) + assertFalse(broadcast.isClosedForSend) val first = broadcast.openSubscription() launch(coroutineContext, CoroutineStart.UNDISPATCHED) { expect(2) - assertThat(first.receive(), IsEqual(1)) // suspends - assertThat(first.isClosedForReceive, IsEqual(false)) + assertEquals(1, first.receive()) // suspends + assertFalse(first.isClosedForReceive) expect(5) - assertThat(first.receive(), IsEqual(2)) // suspends - assertThat(first.isClosedForReceive, IsEqual(false)) + assertEquals(2, first.receive()) // suspends + assertFalse(first.isClosedForReceive) expect(10) - assertThat(first.receiveOrNull(), IsNull()) // suspends - assertThat(first.isClosedForReceive, IsEqual(true)) + assertNull(first.receiveOrNull()) // suspends + assertTrue(first.isClosedForReceive) expect(14) } expect(3) @@ -46,14 +45,15 @@ class ArrayBroadcastChannelTest : TestBase() { expect(4) yield() // to the first receiver expect(6) + val second = broadcast.openSubscription() launch(coroutineContext, CoroutineStart.UNDISPATCHED) { expect(7) - assertThat(second.receive(), IsEqual(2)) // suspends - assertThat(second.isClosedForReceive, IsEqual(false)) + assertEquals(2, second.receive()) // suspends + assertFalse(second.isClosedForReceive) expect(11) - assertThat(second.receiveOrNull(), IsNull()) // suspends - assertThat(second.isClosedForReceive, IsEqual(true)) + assertNull(second.receiveOrNull()) // suspends + assertTrue(second.isClosedForReceive) expect(15) } expect(8) @@ -63,21 +63,21 @@ class ArrayBroadcastChannelTest : TestBase() { expect(12) broadcast.close() expect(13) - assertThat(broadcast.isClosedForSend, IsEqual(true)) + assertTrue(broadcast.isClosedForSend) yield() // to first & second receivers finish(16) } @Test - fun testSendSuspend() = runBlocking { + fun testSendSuspend() = runTest { expect(1) val broadcast = ArrayBroadcastChannel(1) val first = broadcast.openSubscription() launch(coroutineContext) { expect(4) - assertThat(first.receive(), IsEqual(1)) + assertEquals(1, first.receive()) expect(5) - assertThat(first.receive(), IsEqual(2)) + assertEquals(2, first.receive()) expect(6) } expect(2) @@ -88,7 +88,7 @@ class ArrayBroadcastChannelTest : TestBase() { } @Test - fun testConcurrentSendCompletion() = runBlocking { + fun testConcurrentSendCompletion() = runTest { expect(1) val broadcast = ArrayBroadcastChannel(1) val sub = broadcast.openSubscription() @@ -104,17 +104,17 @@ class ArrayBroadcastChannelTest : TestBase() { broadcast.close() // now must receive all 3 items expect(6) - assertThat(sub.isClosedForReceive, IsEqual(false)) + assertFalse(sub.isClosedForReceive) for (x in 1..3) - assertThat(sub.receiveOrNull(), IsEqual(x)) + assertEquals(x, sub.receiveOrNull()) // and receive close signal - assertThat(sub.receiveOrNull(), IsNull()) - assertThat(sub.isClosedForReceive, IsEqual(true)) + assertNull(sub.receiveOrNull()) + assertTrue(sub.isClosedForReceive) finish(7) } @Test - fun testForgetUnsubscribed() = runBlocking { + fun testForgetUnsubscribed() = runTest { expect(1) val broadcast = ArrayBroadcastChannel(1) broadcast.send(1) @@ -124,7 +124,7 @@ class ArrayBroadcastChannelTest : TestBase() { val sub = broadcast.openSubscription() launch(coroutineContext, CoroutineStart.UNDISPATCHED) { expect(3) - assertThat(sub.receive(), IsEqual(4)) // suspends + assertEquals(4, sub.receive()) // suspends expect(5) } expect(4) @@ -134,7 +134,7 @@ class ArrayBroadcastChannelTest : TestBase() { } @Test - fun testReceiveFullAfterClose() = runBlocking { + fun testReceiveFullAfterClose() = runTest { val channel = BroadcastChannel(10) val sub = channel.openSubscription() // generate into buffer & close @@ -148,7 +148,7 @@ class ArrayBroadcastChannelTest : TestBase() { } @Test - fun testCloseSubDuringIteration() = runBlocking { + fun testCloseSubDuringIteration() = runTest { val channel = BroadcastChannel(1) // launch generator (for later) in this context launch(coroutineContext) { @@ -168,9 +168,7 @@ class ArrayBroadcastChannelTest : TestBase() { } @Test - fun testReceiveFromClosedSub() = runTest( - expected = { it is ClosedReceiveChannelException } - ) { + fun testReceiveFromClosedSub() = runTest({ it is ClosedReceiveChannelException }) { val channel = BroadcastChannel(1) val sub = channel.openSubscription() assertFalse(sub.isClosedForReceive) @@ -178,4 +176,4 @@ class ArrayBroadcastChannelTest : TestBase() { assertTrue(sub.isClosedForReceive) sub.receive() } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt similarity index 85% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt index 80d3682127..61fdaefd83 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelTest.kt @@ -17,13 +17,13 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class ArrayChannelTest : TestBase() { + @Test - fun testSimple() = runBlocking { + fun testSimple() = runTest { val q = ArrayChannel(1) check(q.isEmpty && !q.isFull) expect(1) @@ -52,25 +52,7 @@ class ArrayChannelTest : TestBase() { } @Test - fun testStress() = runBlocking { - val n = 100_000 - val q = ArrayChannel(1) - val sender = launch(coroutineContext) { - for (i in 1..n) q.send(i) - expect(2) - } - val receiver = launch(coroutineContext) { - for (i in 1..n) check(q.receive() == i) - expect(3) - } - expect(1) - sender.join() - receiver.join() - finish(4) - } - - @Test - fun testClosedBufferedReceiveOrNull() = runBlocking { + fun testClosedBufferedReceiveOrNull() = runTest { val q = ArrayChannel(1) check(q.isEmpty && !q.isFull && !q.isClosedForSend && !q.isClosedForReceive) expect(1) @@ -95,7 +77,7 @@ class ArrayChannelTest : TestBase() { } @Test - fun testClosedExceptions() = runBlocking { + fun testClosedExceptions() = runTest { val q = ArrayChannel(1) expect(1) launch(coroutineContext) { @@ -106,7 +88,8 @@ class ArrayChannelTest : TestBase() { } } expect(2) - q.close() + + require(q.close()) expect(3) yield() expect(6) @@ -117,7 +100,7 @@ class ArrayChannelTest : TestBase() { } @Test - fun testOfferAndPool() = runBlocking { + fun testOfferAndPool() = runTest { val q = ArrayChannel(1) assertTrue(q.offer(1)) expect(1) @@ -147,7 +130,7 @@ class ArrayChannelTest : TestBase() { } @Test - fun testConsumeAll() = runBlocking { + fun testConsumeAll() = runTest { val q = ArrayChannel(5) for (i in 1..10) { if (i <= 5) { @@ -168,4 +151,4 @@ class ArrayChannelTest : TestBase() { check(q.receiveOrNull() == null) finish(12) } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt similarity index 58% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt index dd60d2426d..2bbc4a1da3 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelFactoryTest.kt @@ -16,34 +16,34 @@ package kotlinx.coroutines.experimental.channels -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.core.IsInstanceOf -import org.junit.Test +import kotlin.test.* + class BroadcastChannelFactoryTest { - @Test(expected = IllegalArgumentException::class) + + @Test fun testRendezvousChannelNotSupported() { - BroadcastChannel(0) + assertFailsWith { BroadcastChannel(0) } } - @Test(expected = IllegalArgumentException::class) + @Test fun testLinkedListChannelNotSupported() { - BroadcastChannel(Channel.UNLIMITED) + assertFailsWith { BroadcastChannel(Channel.UNLIMITED) } } @Test fun testConflatedBroadcastChannel() { - assertThat(BroadcastChannel(Channel.CONFLATED), IsInstanceOf(ConflatedBroadcastChannel::class.java)) + assertTrue { BroadcastChannel(Channel.CONFLATED) is ConflatedBroadcastChannel } } @Test fun testArrayBroadcastChannel() { - assertThat(BroadcastChannel(1), IsInstanceOf(ArrayBroadcastChannel::class.java)) - assertThat(BroadcastChannel(10), IsInstanceOf(ArrayBroadcastChannel::class.java)) + assertTrue { BroadcastChannel(1) is ArrayBroadcastChannel } + assertTrue { BroadcastChannel(10) is ArrayBroadcastChannel } } - @Test(expected = IllegalArgumentException::class) + @Test fun testInvalidCapacityNotSupported() { - BroadcastChannel(-2) + assertFailsWith { BroadcastChannel(-2) } } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt similarity index 54% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt index 5ea2bf21e4..d4c5126601 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelFactoryTest.kt @@ -16,35 +16,36 @@ package kotlinx.coroutines.experimental.channels -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.core.IsInstanceOf -import org.junit.Test +import kotlinx.coroutines.experimental.* +import kotlin.test.* + + +class ChannelFactoryTest : TestBase() { -class ChannelFactoryTest { @Test fun testRendezvousChannel() { - assertThat(Channel(), IsInstanceOf(RendezvousChannel::class.java)) - assertThat(Channel(0), IsInstanceOf(RendezvousChannel::class.java)) + assertTrue(Channel() is RendezvousChannel) + assertTrue(Channel(0) is RendezvousChannel) } @Test fun testLinkedListChannel() { - assertThat(Channel(Channel.UNLIMITED), IsInstanceOf(LinkedListChannel::class.java)) + assertTrue(Channel(Channel.UNLIMITED) is LinkedListChannel) } @Test fun testConflatedChannel() { - assertThat(Channel(Channel.CONFLATED), IsInstanceOf(ConflatedChannel::class.java)) + assertTrue(Channel(Channel.CONFLATED) is ConflatedChannel) } @Test fun testArrayChannel() { - assertThat(Channel(1), IsInstanceOf(ArrayChannel::class.java)) - assertThat(Channel(10), IsInstanceOf(ArrayChannel::class.java)) + assertTrue(Channel(1) is ArrayChannel) + assertTrue(Channel(10) is ArrayChannel) } - @Test(expected = IllegalArgumentException::class) - fun testInvalidCapacityNotSupported() { + @Test + fun testInvalidCapacityNotSupported() = runTest({ it is IllegalArgumentException }) { Channel(-2) } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsTest.kt similarity index 77% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsTest.kt index cdb4f4537b..0fe1fc9749 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsTest.kt @@ -16,87 +16,72 @@ package kotlinx.coroutines.experimental.channels -import kotlinx.coroutines.experimental.async -import kotlinx.coroutines.experimental.runBlocking -import org.junit.Assert.* -import org.junit.Test +import kotlinx.coroutines.experimental.* +import kotlin.math.* +import kotlin.test.* -class ChannelsTest { +class ChannelsTest: TestBase() { private val testList = listOf(1, 2, 3) @Test - fun testBlocking() { - val ch = Channel() - val sum = async { - ch.sumBy { it } - } - repeat(10) { - ch.sendBlocking(it) - } - ch.close() - assertEquals(45, runBlocking { sum.await() }) - - } - - @Test - fun testIterableAsReceiveChannel() = runBlocking { + fun testIterableAsReceiveChannel() = runTest { assertEquals(testList, testList.asReceiveChannel().toList()) } @Test - fun testSequenceAsReceiveChannel() = runBlocking { + fun testSequenceAsReceiveChannel() = runTest { assertEquals(testList, testList.asSequence().asReceiveChannel().toList()) } @Test - fun testAssociate() = runBlocking { + fun testAssociate() = runTest { assertEquals(testList.associate { it * 2 to it * 3 }, testList.asReceiveChannel().associate { it * 2 to it * 3 }.toMap()) } @Test - fun testAssociateBy() = runBlocking { + fun testAssociateBy() = runTest { assertEquals(testList.associateBy { it % 2 }, testList.asReceiveChannel().associateBy { it % 2 }) } @Test - fun testAssociateBy2() = runBlocking { + fun testAssociateBy2() = runTest { assertEquals(testList.associateBy({ it * 2}, { it * 3 }), testList.asReceiveChannel().associateBy({ it * 2}, { it * 3 }).toMap()) } @Test - fun testDistinct() = runBlocking { + fun testDistinct() = runTest { assertEquals(testList.map { it % 2 }.distinct(), testList.asReceiveChannel().map { it % 2 }.distinct().toList()) } @Test - fun testDistinctBy() = runBlocking { + fun testDistinctBy() = runTest { assertEquals(testList.distinctBy { it % 2 }.toList(), testList.asReceiveChannel().distinctBy { it % 2 }.toList()) } @Test - fun testToCollection() = runBlocking { + fun testToCollection() = runTest { val target = mutableListOf() testList.asReceiveChannel().toCollection(target) assertEquals(testList, target) } @Test - fun testDrop() = runBlocking { + fun testDrop() = runTest { for (i in 0..testList.size) { - assertEquals("Drop $i", testList.drop(i), testList.asReceiveChannel().drop(i).toList()) + assertEquals(testList.drop(i), testList.asReceiveChannel().drop(i).toList(), "Drop $i") } } @Test - fun testElementAtOrElse() = runBlocking { + fun testElementAtOrElse() = runTest { assertEquals(testList.elementAtOrElse(2) { 42 }, testList.asReceiveChannel().elementAtOrElse(2) { 42 }) assertEquals(testList.elementAtOrElse(9) { 42 }, testList.asReceiveChannel().elementAtOrElse(9) { 42 }) } @Test - fun testFirst() = runBlocking { + fun testFirst() = runTest { assertEquals(testList.first(), testList.asReceiveChannel().first()) for (i in testList) { assertEquals(testList.first { it == i }, testList.asReceiveChannel().first { it == i }) @@ -109,56 +94,56 @@ class ChannelsTest { } @Test - fun testFirstOrNull() = runBlocking { + fun testFirstOrNull() = runTest { assertEquals(testList.firstOrNull(), testList.asReceiveChannel().firstOrNull()) assertEquals(testList.firstOrNull { it == 2 }, testList.asReceiveChannel().firstOrNull { it == 2 }) assertEquals(testList.firstOrNull { it == 9 }, testList.asReceiveChannel().firstOrNull { it == 9 }) } @Test - fun testFlatMap() = runBlocking { + fun testFlatMap() = runTest { assertEquals(testList.flatMap { (0..it).toList() }, testList.asReceiveChannel().flatMap { (0..it).asReceiveChannel() }.toList()) } @Test - fun testFold() = runBlocking { + fun testFold() = runTest { assertEquals(testList.fold(mutableListOf(42)) { acc, e -> acc.apply { add(e) } }, testList.asReceiveChannel().fold(mutableListOf(42)) { acc, e -> acc.apply { add(e) } }.toList()) } @Test - fun testFoldIndexed() = runBlocking { + fun testFoldIndexed() = runTest { assertEquals(testList.foldIndexed(mutableListOf(42)) { index, acc, e -> acc.apply { add(index + e) } }, testList.asReceiveChannel().foldIndexed(mutableListOf(42)) { index, acc, e -> acc.apply { add(index + e) } }.toList()) } @Test - fun testGroupBy() = runBlocking { + fun testGroupBy() = runTest { assertEquals(testList.groupBy { it % 2 }, testList.asReceiveChannel().groupBy { it % 2 }) } @Test - fun testGroupBy2() = runBlocking { + fun testGroupBy2() = runTest { assertEquals(testList.groupBy({ -it }, { it + 100 }), testList.asReceiveChannel().groupBy({ -it }, { it + 100 }).toMap()) } @Test - fun testMap() = runBlocking { + fun testMap() = runTest { assertEquals(testList.map { it + 10 }, testList.asReceiveChannel().map { it + 10 }.toList()) } @Test - fun testMapToCollection() = runBlocking { + fun testMapToCollection() = runTest { val c = mutableListOf() testList.asReceiveChannel().mapTo(c) { it + 10 } assertEquals(testList.map { it + 10 }, c) } @Test - fun testMapToSendChannel() = runBlocking { + fun testMapToSendChannel() = runTest { val c = produce { testList.asReceiveChannel().mapTo(channel) { it + 10 } } @@ -166,34 +151,34 @@ class ChannelsTest { } @Test - fun testEmptyList() = runBlocking { + fun testEmptyList() = runTest { assertTrue(emptyList().asReceiveChannel().toList().isEmpty()) } @Test - fun testToList() = runBlocking { + fun testToList() = runTest { assertEquals(testList, testList.asReceiveChannel().toList()) } @Test - fun testEmptySet() = runBlocking { + fun testEmptySet() = runTest { assertTrue(emptyList().asReceiveChannel().toSet().isEmpty()) } @Test - fun testToSet() = runBlocking { + fun testToSet() = runTest { assertEquals(testList.toSet(), testList.asReceiveChannel().toSet()) } @Test - fun testToMutableSet() = runBlocking { + fun testToMutableSet() = runTest { assertEquals(testList.toMutableSet(), testList.asReceiveChannel().toMutableSet()) } @Test - fun testEmptySequence() = runBlocking { + fun testEmptySequence() = runTest { val channel = Channel() channel.close() @@ -201,7 +186,7 @@ class ChannelsTest { } @Test - fun testEmptyMap() = runBlocking { + fun testEmptyMap() = runTest { val channel = Channel>() channel.close() @@ -209,50 +194,50 @@ class ChannelsTest { } @Test - fun testToMap() = runBlocking { + fun testToMap() = runTest { val values = testList.map { it to it.toString() } assertEquals(values.toMap(), values.asReceiveChannel().toMap()) } @Test - fun testReduce() = runBlocking { + fun testReduce() = runTest { assertEquals(testList.reduce { acc, e -> acc * e }, testList.asReceiveChannel().reduce { acc, e -> acc * e }) } @Test - fun testReduceIndexed() = runBlocking { + fun testReduceIndexed() = runTest { assertEquals(testList.reduceIndexed { index, acc, e -> index + acc * e }, testList.asReceiveChannel().reduceIndexed { index, acc, e -> index + acc * e }) } @Test - fun testTake() = runBlocking { + fun testTake() = runTest { for (i in 0..testList.size) { assertEquals(testList.take(i), testList.asReceiveChannel().take(i).toList()) } } @Test - fun testPartition() = runBlocking { + fun testPartition() = runTest { assertEquals(testList.partition { it % 2 == 0 }, testList.asReceiveChannel().partition { it % 2 == 0 }) } @Test - fun testZip() = runBlocking { + fun testZip() = runTest { val other = listOf("a", "b") assertEquals(testList.zip(other), testList.asReceiveChannel().zip(other.asReceiveChannel()).toList()) } @Test - fun testElementAt() = runBlocking { + fun testElementAt() = runTest { testList.indices.forEach { i -> assertEquals(testList[i], testList.asReceiveChannel().elementAt(i)) } } @Test - fun testElementAtOrNull() = runBlocking { + fun testElementAtOrNull() = runTest { testList.indices.forEach { i -> assertEquals(testList[i], testList.asReceiveChannel().elementAtOrNull(i)) } @@ -261,7 +246,7 @@ class ChannelsTest { } @Test - fun testFind() = runBlocking { + fun testFind() = runTest { repeat(3) { mod -> assertEquals(testList.find { it % 2 == mod }, testList.asReceiveChannel().find { it % 2 == mod }) @@ -269,28 +254,28 @@ class ChannelsTest { } @Test - fun testFindLast() = runBlocking { + fun testFindLast() = runTest { repeat(3) { mod -> assertEquals(testList.findLast { it % 2 == mod }, testList.asReceiveChannel().findLast { it % 2 == mod }) } } @Test - fun testIndexOf() = runBlocking { + fun testIndexOf() = runTest { repeat(testList.size + 1) { i -> assertEquals(testList.indexOf(i), testList.asReceiveChannel().indexOf(i)) } } @Test - fun testLastIndexOf() = runBlocking { + fun testLastIndexOf() = runTest { repeat(testList.size + 1) { i -> assertEquals(testList.lastIndexOf(i), testList.asReceiveChannel().lastIndexOf(i)) } } @Test - fun testIndexOfFirst() = runBlocking { + fun testIndexOfFirst() = runTest { repeat(3) { mod -> assertEquals(testList.indexOfFirst { it % 2 == mod }, testList.asReceiveChannel().indexOfFirst { it % 2 == mod }) @@ -298,7 +283,7 @@ class ChannelsTest { } @Test - fun testIndexOfLast() = runBlocking { + fun testIndexOfLast() = runTest { repeat(3) { mod -> assertEquals(testList.indexOfLast { it % 2 != mod }, testList.asReceiveChannel().indexOfLast { it % 2 != mod }) @@ -306,13 +291,13 @@ class ChannelsTest { } @Test - fun testLastOrNull() = runBlocking { + fun testLastOrNull() = runTest { assertEquals(testList.lastOrNull(), testList.asReceiveChannel().lastOrNull()) assertEquals(null, emptyList().asReceiveChannel().lastOrNull()) } @Test - fun testSingleOrNull() = runBlocking { + fun testSingleOrNull() = runTest { assertEquals(1, listOf(1).asReceiveChannel().singleOrNull()) assertEquals(null, listOf(1, 2).asReceiveChannel().singleOrNull()) assertEquals(null, emptyList().asReceiveChannel().singleOrNull()) @@ -327,7 +312,7 @@ class ChannelsTest { } @Test - fun testDropWhile() = runBlocking { + fun testDropWhile() = runTest { repeat(3) { mod -> assertEquals(testList.dropWhile { it % 2 == mod }, testList.asReceiveChannel().dropWhile { it % 2 == mod }.toList()) @@ -335,7 +320,7 @@ class ChannelsTest { } @Test - fun testFilter() = runBlocking { + fun testFilter() = runTest { repeat(3) { mod -> assertEquals(testList.filter { it % 2 == mod }, testList.asReceiveChannel().filter { it % 2 == mod }.toList()) @@ -343,7 +328,7 @@ class ChannelsTest { } @Test - fun testFilterToCollection() = runBlocking { + fun testFilterToCollection() = runTest { repeat(3) { mod -> val c = mutableListOf() testList.asReceiveChannel().filterTo(c) { it % 2 == mod } @@ -352,7 +337,7 @@ class ChannelsTest { } @Test - fun testFilterToSendChannel() = runBlocking { + fun testFilterToSendChannel() = runTest { repeat(3) { mod -> val c = produce { testList.asReceiveChannel().filterTo(channel) { it % 2 == mod } @@ -362,7 +347,7 @@ class ChannelsTest { } @Test - fun testFilterNot() = runBlocking { + fun testFilterNot() = runTest { repeat(3) { mod -> assertEquals(testList.filterNot { it % 2 == mod }, testList.asReceiveChannel().filterNot { it % 2 == mod }.toList()) @@ -370,7 +355,7 @@ class ChannelsTest { } @Test - fun testFilterNotToCollection() = runBlocking { + fun testFilterNotToCollection() = runTest { repeat(3) { mod -> val c = mutableListOf() testList.asReceiveChannel().filterNotTo(c) { it % 2 == mod } @@ -379,7 +364,7 @@ class ChannelsTest { } @Test - fun testFilterNotToSendChannel() = runBlocking { + fun testFilterNotToSendChannel() = runTest { repeat(3) { mod -> val c = produce { testList.asReceiveChannel().filterNotTo(channel) { it % 2 == mod } @@ -389,7 +374,7 @@ class ChannelsTest { } @Test - fun testFilterNotNull() = runBlocking { + fun testFilterNotNull() = runTest { repeat(3) { mod -> assertEquals(testList.map { it.takeIf { it % 2 == mod } }.filterNotNull(), testList.asReceiveChannel().map { it.takeIf { it % 2 == mod } }.filterNotNull().toList()) @@ -397,7 +382,7 @@ class ChannelsTest { } @Test - fun testFilterNotNullToCollection() = runBlocking { + fun testFilterNotNullToCollection() = runTest { repeat(3) { mod -> val c = mutableListOf() testList.asReceiveChannel().map { it.takeIf { it % 2 == mod } }.filterNotNullTo(c) @@ -406,7 +391,7 @@ class ChannelsTest { } @Test - fun testFilterNotNullToSendChannel() = runBlocking { + fun testFilterNotNullToSendChannel() = runTest { repeat(3) { mod -> val c = produce { testList.asReceiveChannel().map { it.takeIf { it % 2 == mod } }.filterNotNullTo(channel) @@ -416,7 +401,7 @@ class ChannelsTest { } @Test - fun testFilterIndexed() = runBlocking { + fun testFilterIndexed() = runTest { repeat(3) { mod -> assertEquals(testList.filterIndexed { index, _ -> index % 2 == mod }, testList.asReceiveChannel().filterIndexed { index, _ -> index % 2 == mod }.toList()) @@ -424,7 +409,7 @@ class ChannelsTest { } @Test - fun testFilterIndexedToCollection() = runBlocking { + fun testFilterIndexedToCollection() = runTest { repeat(3) { mod -> val c = mutableListOf() testList.asReceiveChannel().filterIndexedTo(c) { index, _ -> index % 2 == mod } @@ -433,17 +418,17 @@ class ChannelsTest { } @Test - fun testFilterIndexedToChannel() = runBlocking { + fun testFilterIndexedToChannel() = runTest { repeat(3) { mod -> val c = produce { - testList.asReceiveChannel().filterIndexedTo(channel) { index, _ -> index % 2 == mod } + testList.asReceiveChannel().filterIndexedTo(channel) { index, _ -> index % 2 == mod } } assertEquals(testList.filterIndexed { index, _ -> index % 2 == mod }, c.toList()) } } @Test - fun testTakeWhile() = runBlocking { + fun testTakeWhile() = runTest { repeat(3) { mod -> assertEquals(testList.takeWhile { it % 2 != mod }, testList.asReceiveChannel().takeWhile { it % 2 != mod }.toList()) @@ -451,7 +436,7 @@ class ChannelsTest { } @Test - fun testToChannel() = runBlocking { + fun testToChannel() = runTest { val c = produce { testList.asReceiveChannel().toChannel(channel) } @@ -459,20 +444,20 @@ class ChannelsTest { } @Test - fun testMapIndexed() = runBlocking { + fun testMapIndexed() = runTest { assertEquals(testList.mapIndexed { index, i -> index + i }, testList.asReceiveChannel().mapIndexed { index, i -> index + i }.toList()) } @Test - fun testMapIndexedToCollection() = runBlocking { + fun testMapIndexedToCollection() = runTest { val c = mutableListOf() testList.asReceiveChannel().mapIndexedTo(c) { index, i -> index + i } assertEquals(testList.mapIndexed { index, i -> index + i }, c) } @Test - fun testMapIndexedToSendChannel() = runBlocking { + fun testMapIndexedToSendChannel() = runTest { val c = produce { testList.asReceiveChannel().mapIndexedTo(channel) { index, i -> index + i } } @@ -480,7 +465,7 @@ class ChannelsTest { } @Test - fun testMapNotNull() = runBlocking { + fun testMapNotNull() = runTest { repeat(3) { mod -> assertEquals(testList.mapNotNull { i -> i.takeIf { i % 2 == mod } }, testList.asReceiveChannel().mapNotNull { i -> i.takeIf { i % 2 == mod } }.toList()) @@ -488,7 +473,7 @@ class ChannelsTest { } @Test - fun testMapNotNullToCollection() = runBlocking { + fun testMapNotNullToCollection() = runTest { repeat(3) { mod -> val c = mutableListOf() testList.asReceiveChannel().mapNotNullTo(c) { i -> i.takeIf { i % 2 == mod } } @@ -497,7 +482,7 @@ class ChannelsTest { } @Test - fun testMapNotNullToSendChannel() = runBlocking { + fun testMapNotNullToSendChannel() = runTest { repeat(3) { mod -> val c = produce { testList.asReceiveChannel().mapNotNullTo(channel) { i -> i.takeIf { i % 2 == mod } } @@ -507,7 +492,7 @@ class ChannelsTest { } @Test - fun testMapIndexedNotNull() = runBlocking { + fun testMapIndexedNotNull() = runTest { repeat(3) { mod -> assertEquals(testList.mapIndexedNotNull { index, i -> index.takeIf { i % 2 == mod } }, testList.asReceiveChannel().mapIndexedNotNull { index, i -> index.takeIf { i % 2 == mod } }.toList()) @@ -515,7 +500,7 @@ class ChannelsTest { } @Test - fun testMapIndexedNotNullToCollection() = runBlocking { + fun testMapIndexedNotNullToCollection() = runTest { repeat(3) { mod -> val c = mutableListOf() testList.asReceiveChannel().mapIndexedNotNullTo(c) { index, i -> index.takeIf { i % 2 == mod } } @@ -524,7 +509,7 @@ class ChannelsTest { } @Test - fun testMapIndexedNotNullToSendChannel() = runBlocking { + fun testMapIndexedNotNullToSendChannel() = runTest { repeat(3) { mod -> val c = produce { testList.asReceiveChannel().mapIndexedNotNullTo(channel) { index, i -> index.takeIf { i % 2 == mod } } @@ -534,50 +519,51 @@ class ChannelsTest { } @Test - fun testWithIndex() = runBlocking { + fun testWithIndex() = runTest { assertEquals(testList.withIndex().toList(), testList.asReceiveChannel().withIndex().toList()) } @Test - fun testMaxBy() = runBlocking { - assertEquals(testList.maxBy { 10 - Math.abs(it - 2) }, - testList.asReceiveChannel().maxBy { 10 - Math.abs(it - 2) }) + fun testMaxBy() = runTest { + assertEquals(testList.maxBy { 10 - abs(it - 2) }, + testList.asReceiveChannel().maxBy { 10 - abs(it - 2) }) } @Test - fun testMaxWith() = runBlocking { - val cmp = compareBy { 10 - Math.abs(it - 2) } + fun testMaxWith() = runTest { + val cmp = compareBy { 10 - abs(it - 2) } assertEquals(testList.maxWith(cmp), testList.asReceiveChannel().maxWith(cmp)) } @Test - fun testMinBy() = runBlocking { - assertEquals(testList.minBy { Math.abs(it - 2) }, - testList.asReceiveChannel().minBy { Math.abs(it - 2) }) + fun testMinBy() = runTest { + assertEquals(testList.minBy { abs(it - 2) }, + testList.asReceiveChannel().minBy { abs(it - 2) }) } @Test - fun testMinWith() = runBlocking { - val cmp = compareBy { Math.abs(it - 2) } + fun testMinWith() = runTest { + val cmp = compareBy { abs(it - 2) } assertEquals(testList.minWith(cmp), testList.asReceiveChannel().minWith(cmp)) } @Test - fun testSumBy() = runBlocking { + fun testSumBy() = runTest { assertEquals(testList.sumBy { it * 3 }, testList.asReceiveChannel().sumBy { it * 3 }) } @Test - fun testSumByDouble() = runBlocking { - assertEquals(testList.sumByDouble { it * 3.0 }, - testList.asReceiveChannel().sumByDouble { it * 3.0 }, 1e-10) + fun testSumByDouble() = runTest { + val expected = testList.sumByDouble { it * 3.0 } + val actual = testList.asReceiveChannel().sumByDouble { it * 3.0 } + assertEquals(expected, actual) } @Test - fun testRequireNoNulls() = runBlocking { + fun testRequireNoNulls() = runTest { assertEquals(testList.requireNoNulls(), testList.asReceiveChannel().requireNoNulls().toList()) } } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt similarity index 58% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt index 707b780d6e..4c04f8f268 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt @@ -17,85 +17,87 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.core.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class ConflatedBroadcastChannelTest : TestBase() { + @Test - fun testBasicScenario() = runBlocking { + fun testBasicScenario() = runTest { expect(1) val broadcast = ConflatedBroadcastChannel() - assertThat(exceptionFrom { broadcast.value }, IsInstanceOf(IllegalStateException::class.java)) - assertThat(broadcast.valueOrNull, IsNull()) + assertTrue(exceptionFromNotInline { broadcast.value } is IllegalStateException) + assertNull(broadcast.valueOrNull) + launch(coroutineContext, CoroutineStart.UNDISPATCHED) { expect(2) val sub = broadcast.openSubscription() - assertThat(sub.poll(), IsNull()) + assertNull(sub.poll()) expect(3) - assertThat(sub.receive(), IsEqual("one")) // suspends + assertEquals("one", sub.receive()) // suspends expect(6) - assertThat(sub.receive(), IsEqual("two")) // suspends + assertEquals("two", sub.receive()) // suspends expect(12) sub.close() expect(13) } + expect(4) broadcast.send("one") // does not suspend - assertThat(broadcast.value, IsEqual("one")) - assertThat(broadcast.valueOrNull, IsEqual("one")) + assertEquals("one", broadcast.value) + assertEquals("one", broadcast.valueOrNull) expect(5) yield() // to receiver expect(7) launch(coroutineContext, CoroutineStart.UNDISPATCHED) { expect(8) val sub = broadcast.openSubscription() - assertThat(sub.receive(), IsEqual("one")) // does not suspend + assertEquals("one", sub.receive()) // does not suspend expect(9) - assertThat(sub.receive(), IsEqual("two")) // suspends + assertEquals("two", sub.receive()) // suspends expect(14) - assertThat(sub.receive(), IsEqual("three")) // suspends + assertEquals("three", sub.receive()) // suspends expect(17) - assertThat(sub.receiveOrNull(), IsNull()) // suspends until closed + assertNull(sub.receiveOrNull()) // suspends until closed expect(20) sub.close() expect(21) } + expect(10) broadcast.send("two") // does not suspend - assertThat(broadcast.value, IsEqual("two")) - assertThat(broadcast.valueOrNull, IsEqual("two")) + assertEquals("two", broadcast.value) + assertEquals("two", broadcast.valueOrNull) expect(11) yield() // to both receivers expect(15) broadcast.send("three") // does not suspend - assertThat(broadcast.value, IsEqual("three")) - assertThat(broadcast.valueOrNull, IsEqual("three")) + assertEquals("three", broadcast.value) + assertEquals("three", broadcast.valueOrNull) expect(16) yield() // to second receiver expect(18) broadcast.close() - assertThat(exceptionFrom { broadcast.value }, IsInstanceOf(IllegalStateException::class.java)) - assertThat(broadcast.valueOrNull, IsNull()) + assertTrue(exceptionFromNotInline { broadcast.value } is IllegalStateException) + assertNull(broadcast.valueOrNull) expect(19) yield() // to second receiver - assertThat(exceptionFrom { broadcast.send("four") }, IsInstanceOf(ClosedSendChannelException::class.java)) + assertTrue(exceptionFrom { broadcast.send("four") } is ClosedSendChannelException) finish(22) } @Test - fun testInitialValueAndReceiveClosed() = runBlocking { + fun testInitialValueAndReceiveClosed() = runTest { expect(1) - val broadcast = ConflatedBroadcastChannel(1) - assertThat(broadcast.value, IsEqual(1)) - assertThat(broadcast.valueOrNull, IsEqual(1)) + val broadcast = ConflatedBroadcastChannel(1) + assertEquals(1, broadcast.value) + assertEquals(1, broadcast.valueOrNull) launch(coroutineContext, CoroutineStart.UNDISPATCHED) { expect(2) val sub = broadcast.openSubscription() - assertThat(sub.receive(), IsEqual(1)) + assertEquals(1, sub.receive()) expect(3) - assertThat(exceptionFrom { sub.receive() }, IsInstanceOf(ClosedReceiveChannelException::class.java)) // suspends + assertTrue(exceptionFrom { sub.receive() } is ClosedReceiveChannelException) // suspends expect(6) } expect(4) @@ -113,4 +115,14 @@ class ConflatedBroadcastChannelTest : TestBase() { return e } } -} \ No newline at end of file + + // Ugly workaround for bug in JS compiler + fun exceptionFromNotInline(block: () -> Unit): Throwable? { + try { + block() + return null + } catch (e: Throwable) { + return e + } + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt similarity index 71% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt index 6819170f0e..1fd7413495 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedChannelTest.kt @@ -17,50 +17,49 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.core.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class ConflatedChannelTest : TestBase() { + @Test fun testBasicConflationOfferPoll() { val q = ConflatedChannel() - assertThat(q.poll(), IsNull()) - assertThat(q.offer(1), IsEqual(true)) - assertThat(q.offer(2), IsEqual(true)) - assertThat(q.offer(3), IsEqual(true)) - assertThat(q.poll(), IsEqual(3)) - assertThat(q.poll(), IsNull()) + assertNull(q.poll()) + assertTrue(q.offer(1)) + assertTrue(q.offer(2)) + assertTrue(q.offer(3)) + assertEquals(3, q.poll()) + assertNull(q.poll()) } @Test - fun testConflatedSend() = runBlocking { + fun testConflatedSend() = runTest { val q = ConflatedChannel() q.send(1) q.send(2) // shall conflated previously sent - assertThat(q.receiveOrNull(), IsEqual(2)) + assertEquals(2, q.receiveOrNull()) } @Test - fun testConflatedClose() = runBlocking { + fun testConflatedClose() = runTest { val q = ConflatedChannel() q.send(1) q.close() // shall conflate sent item and become closed - assertThat(q.receiveOrNull(), IsNull()) + assertNull(q.receiveOrNull()) } @Test - fun testConflationSendReceive() = runBlocking { + fun testConflationSendReceive() = runTest { val q = ConflatedChannel() expect(1) launch(coroutineContext) { // receiver coroutine expect(4) - assertThat(q.receive(), IsEqual(2)) + assertEquals(2, q.receive()) expect(5) - assertThat(q.receive(), IsEqual(3)) // this receive suspends + assertEquals(3, q.receive()) // this receive suspends expect(8) - assertThat(q.receive(), IsEqual(6)) // last conflated value + assertEquals(6, q.receive()) // last conflated value expect(9) } expect(2) @@ -79,7 +78,7 @@ class ConflatedChannelTest : TestBase() { } @Test - fun testConsumeAll() = runBlocking { + fun testConsumeAll() = runTest { val q = ConflatedChannel() expect(1) for (i in 1..10) { diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt similarity index 72% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt index 95a6c119d9..897801e378 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/LinkedListChannelTest.kt @@ -17,29 +17,26 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.TestBase -import kotlinx.coroutines.experimental.runBlocking -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.core.IsEqual -import org.hamcrest.core.IsNull -import org.junit.Test +import kotlin.test.* class LinkedListChannelTest : TestBase() { + @Test - fun testBasic() = runBlocking { + fun testBasic() = runTest { val c = LinkedListChannel() c.send(1) check(c.offer(2)) c.send(3) check(c.close()) check(!c.close()) - assertThat(c.receive(), IsEqual(1)) - assertThat(c.poll(), IsEqual(2)) - assertThat(c.receiveOrNull(), IsEqual(3)) - assertThat(c.receiveOrNull(), IsNull()) + assertEquals(1, c.receive()) + assertEquals(2, c.poll()) + assertEquals(3, c.receiveOrNull()) + assertNull(c.receiveOrNull()) } @Test - fun testConsumeAll() = runBlocking { + fun testConsumeAll() = runTest { val q = LinkedListChannel() for (i in 1..10) { q.send(i) // buffers @@ -49,4 +46,4 @@ class LinkedListChannelTest : TestBase() { check(q.isClosedForReceive) check(q.receiveOrNull() == null) } -} \ No newline at end of file +} diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt new file mode 100644 index 0000000000..6646aa61e4 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt @@ -0,0 +1,55 @@ +package kotlinx.coroutines.experimental.channels + +import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* +import kotlin.test.* + +class ProduceConsumeTest : TestBase() { + + @Test + fun testRendezvous() = runTest { + testProducer(1) + } + + @Test + fun testSmallBuffer() = runTest { + testProducer(1) + } + + @Test + fun testMediumBuffer() = runTest { + testProducer(10) + } + + @Test + fun testLargeMediumBuffer() = runTest { + testProducer(1000) + } + + @Test + fun testUnlimited() = runTest { + testProducer(Channel.UNLIMITED) + } + + private suspend fun testProducer(producerCapacity: Int) { + testProducer(1, producerCapacity) + testProducer(10, producerCapacity) + testProducer(100, producerCapacity) + } + + private suspend fun testProducer(messages: Int, producerCapacity: Int) { + var sentAll = false + val producer = produce(coroutineContext, capacity = producerCapacity) { + for (i in 1..messages) { + send(i) + } + sentAll = true + } + var consumed = 0 + for (x in producer) { + consumed++ + } + assertTrue(sentAll) + assertEquals(messages, consumed) + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt similarity index 98% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt index 641eff0f62..522f6d6f6b 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceTest.kt @@ -17,10 +17,11 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.junit.* import kotlin.coroutines.experimental.* +import kotlin.test.* class ProduceTest : TestBase() { + @Test fun testBasic() = runTest { val c = produce(coroutineContext) { @@ -62,4 +63,4 @@ class ProduceTest : TestBase() { check(c.receiveOrNull() == null) expect(6) } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt similarity index 85% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt index 7edbe42bf6..6e1b2c347a 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/RendezvousChannelTest.kt @@ -17,14 +17,13 @@ package kotlinx.coroutines.experimental.channels import kotlinx.coroutines.experimental.* -import org.hamcrest.core.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class RendezvousChannelTest : TestBase() { + @Test - fun testSimple() = runBlocking { + fun testSimple() = runTest { val q = RendezvousChannel() check(q.isEmpty && q.isFull) expect(1) @@ -51,25 +50,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testStress() = runBlocking { - val n = 100_000 - val q = RendezvousChannel() - val sender = launch(coroutineContext) { - for (i in 1..n) q.send(i) - expect(2) - } - val receiver = launch(coroutineContext) { - for (i in 1..n) check(q.receive() == i) - expect(3) - } - expect(1) - sender.join() - receiver.join() - finish(4) - } - - @Test - fun testClosedReceiveOrNull() = runBlocking { + fun testClosedReceiveOrNull() = runTest { val q = RendezvousChannel() check(q.isEmpty && q.isFull && !q.isClosedForSend && !q.isClosedForReceive) expect(1) @@ -91,7 +72,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testClosedExceptions() = runBlocking { + fun testClosedExceptions() = runTest { val q = RendezvousChannel() expect(1) launch(coroutineContext) { @@ -113,7 +94,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testOfferAndPool() = runBlocking { + fun testOfferAndPool() = runTest { val q = RendezvousChannel() assertFalse(q.offer(1)) expect(1) @@ -141,7 +122,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testIteratorClosed() = runBlocking { + fun testIteratorClosed() = runTest { val q = RendezvousChannel() expect(1) launch(coroutineContext) { @@ -157,7 +138,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testIteratorOne() = runBlocking { + fun testIteratorOne() = runTest { val q = RendezvousChannel() expect(1) launch(coroutineContext) { @@ -176,7 +157,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testIteratorOneWithYield() = runBlocking { + fun testIteratorOneWithYield() = runTest { val q = RendezvousChannel() expect(1) launch(coroutineContext) { @@ -197,7 +178,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testIteratorTwo() = runBlocking { + fun testIteratorTwo() = runTest { val q = RendezvousChannel() expect(1) launch(coroutineContext) { @@ -221,7 +202,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testIteratorTwoWithYield() = runBlocking { + fun testIteratorTwoWithYield() = runTest { val q = RendezvousChannel() expect(1) launch(coroutineContext) { @@ -247,7 +228,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testSuspendSendOnClosedChannel() = runBlocking { + fun testSuspendSendOnClosedChannel() = runTest { val q = RendezvousChannel() expect(1) launch(coroutineContext) { @@ -266,9 +247,9 @@ class RendezvousChannelTest : TestBase() { expect(7) yield() // try to resume sender (it will not resume despite the close!) expect(8) - assertThat(q.receiveOrNull(), IsEqual(42)) + assertEquals(42, q.receiveOrNull()) expect(9) - assertThat(q.receiveOrNull(), IsNull()) + assertNull(q.receiveOrNull()) expect(10) yield() // to sender, it was resumed! finish(12) @@ -281,7 +262,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testProduceBadClass() = runBlocking { + fun testProduceBadClass() = runTest { val bad = BadClass() val c = produce(coroutineContext) { expect(1) @@ -292,7 +273,7 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testConsumeAll() = runBlocking { + fun testConsumeAll() = runTest { val q = RendezvousChannel() for (i in 1..10) { launch(coroutineContext, CoroutineStart.UNDISPATCHED) { @@ -308,4 +289,4 @@ class RendezvousChannelTest : TestBase() { check(q.receiveOrNull() == null) finish(12) } -} \ No newline at end of file +} diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/SendReceiveStressTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/SendReceiveStressTest.kt new file mode 100644 index 0000000000..c85f541362 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/SendReceiveStressTest.kt @@ -0,0 +1,46 @@ +package kotlinx.coroutines.experimental.channels + +import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* +import kotlin.test.* + +class SendReceiveStressTest : TestBase() { + + // Emulate parametrized by hand :( + + @Test + fun testArrayChannel() = runTest { + testStress(ArrayChannel(2)) + } + + @Test + fun testLinkedListChannel() = runTest { + testStress(LinkedListChannel()) + } + + @Test + fun testRendezvousChannel() = runTest { + testStress(RendezvousChannel()) + } + + private suspend fun testStress(channel: Channel) { + val n = 1_000 // Do not increase, otherwise node.js will fail with timeout :( + val sender = launch(coroutineContext) { + for (i in 1..n) { + channel.send(i) + } + expect(2) + } + val receiver = launch(coroutineContext) { + for (i in 1..n) { + val next = channel.receive() + check(next == i) + } + expect(3) + } + expect(1) + sender.join() + receiver.join() + finish(4) + } +} diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt new file mode 100644 index 0000000000..69e939f2d9 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt @@ -0,0 +1,35 @@ +package kotlinx.coroutines.experimental.channels + +import kotlinx.coroutines.experimental.* +import kotlin.coroutines.experimental.* +import kotlin.test.* + +class SimpleSendReceiveTest : TestBase() { + + @Test + fun testSimpleSendReceive() = runTest { + // Parametrized common test :( + TestChannelKind.values().forEach { kind -> testSendReceive(kind, 100) } + } + + private suspend fun testSendReceive(kind: TestChannelKind, iterations: Int) { + val channel = kind.create() + + launch(coroutineContext) { + repeat(iterations) { channel.send(it) } + channel.close() + } + var expected = 0 + for (x in channel) { + if (!kind.isConflated) { + assertEquals(expected++, x) + } else { + assertTrue(x >= expected) + expected = x + 1 + } + } + if (!kind.isConflated) { + assertEquals(iterations, expected) + } + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestBroadcastChannelKind.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestBroadcastChannelKind.kt similarity index 95% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestBroadcastChannelKind.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestBroadcastChannelKind.kt index e025b195bd..60dbb97a5d 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestBroadcastChannelKind.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestBroadcastChannelKind.kt @@ -18,15 +18,15 @@ package kotlinx.coroutines.experimental.channels enum class TestBroadcastChannelKind { ARRAY_1 { - override fun create(): BroadcastChannel = ArrayBroadcastChannel(1) + override fun create(): BroadcastChannel = ArrayBroadcastChannel(1) override fun toString(): String = "ArrayBroadcastChannel(1)" }, ARRAY_10 { - override fun create(): BroadcastChannel = ArrayBroadcastChannel(10) + override fun create(): BroadcastChannel = ArrayBroadcastChannel(10) override fun toString(): String = "ArrayBroadcastChannel(10)" }, CONFLATED { - override fun create(): BroadcastChannel = ConflatedBroadcastChannel() + override fun create(): BroadcastChannel = ConflatedBroadcastChannel() override fun toString(): String = "ConflatedBroadcastChannel" override val isConflated: Boolean get() = true } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt similarity index 82% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt index 36fa8c39b1..c3ac904cd1 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/TestChannelKind.kt @@ -20,23 +20,23 @@ import kotlinx.coroutines.experimental.selects.SelectClause1 enum class TestChannelKind { RENDEZVOUS { - override fun create(): Channel = RendezvousChannel() + override fun create(): Channel = RendezvousChannel() override fun toString(): String = "RendezvousChannel" }, ARRAY_1 { - override fun create(): Channel = ArrayChannel(1) + override fun create(): Channel = ArrayChannel(1) override fun toString(): String = "ArrayChannel(1)" }, ARRAY_10 { - override fun create(): Channel = ArrayChannel(8) + override fun create(): Channel = ArrayChannel(8) override fun toString(): String = "ArrayChannel(8)" }, LINKED_LIST { - override fun create(): Channel = LinkedListChannel() + override fun create(): Channel = LinkedListChannel() override fun toString(): String = "LinkedListChannel" }, CONFLATED { - override fun create(): Channel = ConflatedChannel() + override fun create(): Channel = ConflatedChannel() override fun toString(): String = "ConflatedChannel" override val isConflated: Boolean get() = true }, @@ -66,8 +66,12 @@ private class ChannelViaBroadcast( override val isClosedForReceive: Boolean get() = sub.isClosedForReceive override val isEmpty: Boolean get() = sub.isEmpty - suspend override fun receive(): E = sub.receive() - suspend override fun receiveOrNull(): E? = sub.receiveOrNull() + + // Workaround for KT-23094 + override suspend fun send(element: E) = broadcast.send(element) + + override suspend fun receive(): E = sub.receive() + override suspend fun receiveOrNull(): E? = sub.receiveOrNull() override fun poll(): E? = sub.poll() override fun iterator(): ChannelIterator = sub.iterator() override fun cancel(cause: Throwable?): Boolean = sub.cancel(cause) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelsJvm.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelsJvm.kt new file mode 100644 index 0000000000..7596e62cb9 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelsJvm.kt @@ -0,0 +1,22 @@ +package kotlinx.coroutines.experimental.channels + +import kotlinx.coroutines.experimental.* + +// -------- Operations on SendChannel -------- + +/** + * Adds [element] into to this channel, **blocking** the caller while this channel [Channel.isFull], + * or throws exception if the channel [Channel.isClosedForSend] (see [Channel.close] for details). + * + * This is a way to call [Channel.send] method inside a blocking code using [runBlocking], + * so this function should not be used from coroutine. + */ +public fun SendChannel.sendBlocking(element: E) { + // fast path + if (offer(element)) + return + // slow path + runBlocking { + send(element) + } +} diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt new file mode 100644 index 0000000000..bb365527d0 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt @@ -0,0 +1,5 @@ +package kotlinx.coroutines.experimental.internal + +actual fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int){ + System.arraycopy(source, srcPos, destination, destinationStart, length) +} \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt new file mode 100644 index 0000000000..9552fc2706 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt @@ -0,0 +1,3 @@ +package kotlinx.coroutines.experimental.internal + +actual typealias Closeable = java.io.Closeable diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt new file mode 100644 index 0000000000..1eef996481 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt @@ -0,0 +1,10 @@ +package kotlinx.coroutines.experimental.internal + +import java.util.concurrent.* +import kotlin.concurrent.withLock as withLockJvm + +actual fun subscriberList(): MutableList = CopyOnWriteArrayList() + +actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock + +actual inline fun ReentrantLock.withLock(action: () -> T) = this.withLockJvm(action) diff --git a/core/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 index 057ce9d43a..b99b3fcb41 100644 --- a/core/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 @@ -42,7 +42,7 @@ internal val LIST_EMPTY: Any = Symbol("LIST_EMPTY") private val REMOVE_PREPARED: Any = Symbol("REMOVE_PREPARED") /** @suppress **This is unstable API and it is subject to change.** */ -public typealias RemoveFirstDesc = LockFreeLinkedListNode.RemoveFirstDesc +public actual typealias RemoveFirstDesc = LockFreeLinkedListNode.RemoveFirstDesc /** @suppress **This is unstable API and it is subject to change.** */ public actual typealias AddLastDesc = LockFreeLinkedListNode.AddLastDesc @@ -101,7 +101,7 @@ public actual open class LockFreeLinkedListNode { public actual val isRemoved: Boolean get() = next is Removed // LINEARIZABLE. Returns Node | Removed - public val next: Any get() { + public actual val next: Any get() { _next.loop { next -> if (next !is OpDescriptor) return next next.perform(this) @@ -111,7 +111,7 @@ public actual open class LockFreeLinkedListNode { public actual val nextNode: Node get() = next.unwrap() // LINEARIZABLE. Returns Node | Removed - public val prev: Any get() { + public actual val prev: Any get() { _prev.loop { prev -> if (prev is Removed) return prev prev as Node // otherwise, it can be only node @@ -311,7 +311,7 @@ public actual open class LockFreeLinkedListNode { // ------ multi-word atomic operations helpers ------ - public open class AddLastDesc( + public open class AddLastDesc constructor( @JvmField val queue: Node, @JvmField val node: T ) : AbstractAtomicDesc() { diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelStressTest.kt new file mode 100644 index 0000000000..dd77e1efa4 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannelStressTest.kt @@ -0,0 +1,39 @@ +package kotlinx.coroutines.experimental.channels + +import kotlinx.coroutines.experimental.* +import org.junit.* +import org.junit.runner.* +import org.junit.runners.* + +@RunWith(Parameterized::class) +class ArrayChannelStressTest(private val capacity: Int) : TestBase() { + + companion object { + @Parameterized.Parameters(name = "{0}, nSenders={1}, nReceivers={2}") + @JvmStatic + fun params(): Collection> = listOf(1, 10, 100, 100_000, 1_000_000).map { arrayOf(it) } + } + + @Test + fun testStress() = runTest { + val n = 100_000 * stressTestMultiplier + val q = ArrayChannel(capacity) + val sender = launch(kotlin.coroutines.experimental.coroutineContext) { + for (i in 1..n) { + q.send(i) + } + expect(2) + } + val receiver = launch(kotlin.coroutines.experimental.coroutineContext) { + for (i in 1..n) { + val next = q.receive() + check(next == i) + } + expect(3) + } + expect(1) + sender.join() + receiver.join() + finish(4) + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsJvmTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsJvmTest.kt new file mode 100644 index 0000000000..6a5e509bbe --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ChannelsJvmTest.kt @@ -0,0 +1,21 @@ +package kotlinx.coroutines.experimental.channels + +import kotlinx.coroutines.experimental.* +import org.junit.Test +import kotlin.test.* + +class ChannelsJvmTest : TestBase() { + + @Test + fun testBlocking() { + val ch = Channel() + val sum = async { + ch.sumBy { it } + } + repeat(10) { + ch.sendBlocking(it) + } + ch.close() + assertEquals(45, runBlocking { sum.await() }) + } +} diff --git a/core/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 index 1e4682f762..90a254a429 100644 --- a/core/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 @@ -35,4 +35,4 @@ class DoubleChannelCloseStressTest : TestBase() { actor.close() } } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeJvmTest.kt similarity index 96% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeJvmTest.kt index 8aea78f7a6..9fde116b9e 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/ProduceConsumeJvmTest.kt @@ -24,7 +24,7 @@ import org.junit.runners.* import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) -class ProduceConsumeTest( +class ProduceConsumeJvmTest( private val capacity: Int, private val number: Int ) : TestBase() { @@ -43,7 +43,7 @@ class ProduceConsumeTest( fun testProducer() = runTest { var sentAll = false val producer = produce(coroutineContext, capacity = capacity) { - for(i in 1..number) { + for (i in 1..number) { send(i) } sentAll = true @@ -72,4 +72,4 @@ class ProduceConsumeTest( actor.close() assertEquals(number, received.await()) } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RandevouzChannelStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RandevouzChannelStressTest.kt new file mode 100644 index 0000000000..0faa693042 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/RandevouzChannelStressTest.kt @@ -0,0 +1,26 @@ +package kotlinx.coroutines.experimental.channels + +import kotlinx.coroutines.experimental.* +import org.junit.* +import kotlin.coroutines.experimental.* + +class RandevouzChannelStressTest : TestBase() { + + @Test + fun testStress() = runTest { + val n = 100_000 * stressTestMultiplier + val q = RendezvousChannel() + val sender = launch(coroutineContext) { + for (i in 1..n) q.send(i) + expect(2) + } + val receiver = launch(coroutineContext) { + for (i in 1..n) check(q.receive() == i) + expect(3) + } + expect(1) + sender.join() + receiver.join() + finish(4) + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SendReceiveJvmStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SendReceiveJvmStressTest.kt new file mode 100644 index 0000000000..4cea191d7e --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SendReceiveJvmStressTest.kt @@ -0,0 +1,45 @@ +package kotlinx.coroutines.experimental.channels + +import kotlinx.coroutines.experimental.* +import org.junit.runner.* +import org.junit.runners.* +import kotlin.coroutines.experimental.* +import kotlin.test.* + +@RunWith(Parameterized::class) +class SendReceiveJvmStressTest(private val channel: Channel) : TestBase() { + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun params(): Collection> = listOf( + ArrayChannel(1), + ArrayChannel(10), + ArrayChannel(1_000_000), + LinkedListChannel(), + RendezvousChannel() + ).map { arrayOf(it) } + } + + @Test + fun testStress() = runTest { + val n = 100_000 * stressTestMultiplier + val sender = launch(coroutineContext) { + for (i in 1..n) { + channel.send(i) + } + expect(2) + } + val receiver = launch(coroutineContext) { + for (i in 1..n) { + val next = channel.receive() + check(next == i) + } + expect(3) + } + expect(1) + sender.join() + receiver.join() + finish(4) + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveJvmTest.kt similarity index 98% rename from core/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/SimpleSendReceiveJvmTest.kt index feb06e0438..73639ac3c5 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/SimpleSendReceiveJvmTest.kt @@ -25,7 +25,7 @@ import org.junit.runners.* import kotlin.coroutines.experimental.* @RunWith(Parameterized::class) -class SimpleSendReceiveTest( +class SimpleSendReceiveJvmTest( val kind: TestChannelKind, val n: Int, val concurrent: Boolean @@ -64,4 +64,4 @@ class SimpleSendReceiveTest( assertThat(expected, IsEqual(n)) } } -} \ No newline at end of file +} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt index 37a04377aa..6894ac00a5 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Exceptions.kt @@ -30,7 +30,7 @@ public actual class CompletionHandlerException public actual constructor( * **It is not printed to console/log by default uncaught exception handler**. * (see [handleCoroutineException]). */ -public actual open class CancellationException actual constructor(message: String) : IllegalStateException(message) +public actual open class CancellationException actual constructor(message: String?) : IllegalStateException(message) /** * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt new file mode 100644 index 0000000000..133cb542e1 --- /dev/null +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt @@ -0,0 +1,9 @@ +package kotlinx.coroutines.experimental.internal + + +actual fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int) { + var destinationIndex = destinationStart + for (sourceIndex in srcPos until srcPos + length) { + destination[destinationIndex++] = source[sourceIndex] + } +} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt new file mode 100644 index 0000000000..7888c8e3ba --- /dev/null +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt @@ -0,0 +1,5 @@ +package kotlinx.coroutines.experimental.internal + +actual interface Closeable { + actual fun close() +} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt new file mode 100644 index 0000000000..20dcd6bb4c --- /dev/null +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt @@ -0,0 +1,12 @@ +package kotlinx.coroutines.experimental.internal + +actual typealias ReentrantLock = NoOpLock + +actual inline fun ReentrantLock.withLock(action: () -> T) = action() + +public class NoOpLock { + fun tryLock() = true + fun unlock(): Unit {} +} + +actual fun subscriberList(): MutableList = ArrayList() diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt index 453bf84f02..2569249418 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt @@ -16,6 +16,8 @@ package kotlinx.coroutines.experimental.internal +import kotlinx.coroutines.experimental.channels.* + private typealias Node = LinkedListNode /** @suppress **This is unstable API and it is subject to change.** */ @@ -31,6 +33,9 @@ public open class LinkedListNode { @PublishedApi internal var _prev = this @PublishedApi internal var _removed: Boolean = false + public val prev: Any get() = _prev + public val next: Any get() = _next + public inline val nextNode get() = _next public inline val prevNode get() = _prev public inline val isRemoved get() = _removed @@ -107,6 +112,21 @@ public actual open class AddLastDesc actual constructor( protected override val affectedNode: Node get() = queue._prev protected actual override fun onPrepare(affected: Node, next: Node): Any? = null protected override fun onComplete() = queue.addLast(node) + protected actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit +} + +public actual open class RemoveFirstDesc actual constructor( + actual val queue: LockFreeLinkedListNode +) : AbstractAtomicDesc() { + + @Suppress("UNCHECKED_CAST") + public actual val result: T get() = affectedNode as T + protected override val affectedNode: Node get() = queue._prev + + protected actual open fun validatePrepared(node: T): Boolean = true + protected actual final override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? = null + protected override fun onComplete(): Unit { queue.removeFirstOrNull() } + protected actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit } /** @suppress **This is unstable API and it is subject to change.** */ @@ -116,6 +136,10 @@ public actual abstract class AbstractAtomicDesc : AtomicDesc() { protected abstract fun onComplete() actual final override fun prepare(op: AtomicOp<*>): Any? = onPrepare(affectedNode, affectedNode._next) actual final override fun complete(op: AtomicOp<*>, failure: Any?) = onComplete() + + protected actual open fun failure(affected: LockFreeLinkedListNode, next: Any): Any? = null // Never fails + protected actual open fun retry(affected: LockFreeLinkedListNode, next: Any): Boolean = false // Always succeeds + protected actual abstract fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) } /** @suppress **This is unstable API and it is subject to change.** */ diff --git a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopyKtTest.kt b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopyKtTest.kt new file mode 100644 index 0000000000..564969ac78 --- /dev/null +++ b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopyKtTest.kt @@ -0,0 +1,14 @@ +package kotlinx.coroutines.experimental.internal + +import kotlin.test.* + +class ArrayCopyTest { + + @Test + fun testArrayCopy() { + val source = Array(10, { it }) + val destination = arrayOfNulls(7) + arraycopy(source, 2, destination, 1, 5) + assertEquals(listOf(null, 2, 3, 4, 5, 6, null), destination.toList()) + } +} From 2cdbfd7e48eb671ae8a98462e75c18f3fdcce007 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Sun, 22 Apr 2018 18:26:14 +0300 Subject: [PATCH 49/61] Properly implement select clause for JS channels --- .../experimental/internal/Atomic.kt | 4 +- .../channels/ConflatedBroadcastChannelTest.kt | 2 +- .../selects/SelectArrayChannelTest.kt | 42 +++++------ .../experimental/selects/SelectBiasTest.kt | 9 ++- .../selects/SelectRendezvousChannelTest.kt | 43 +++++------ .../selects/SelectChannelStressTest.kt | 72 +++++++++++++++++++ .../experimental/internal/LinkedList.kt | 26 ++++--- 7 files changed, 140 insertions(+), 58 deletions(-) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt (86%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt (90%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt (87%) create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectChannelStressTest.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt index ce19e05c1c..77b4175c3b 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Atomic.kt @@ -66,8 +66,10 @@ public abstract class AtomicOp : OpDescriptor() { final override fun perform(affected: Any?): Any? { // make decision on status var decision = this._consensus.value - if (decision === NO_DECISION) + if (decision === NO_DECISION) { decision = decide(prepare(affected as T)) + } + complete(affected as T, decision) return decision } diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt index 4c04f8f268..2cd539b5e3 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/channels/ConflatedBroadcastChannelTest.kt @@ -116,7 +116,7 @@ class ConflatedBroadcastChannelTest : TestBase() { } } - // Ugly workaround for bug in JS compiler + // Workaround for KT-23921 fun exceptionFromNotInline(block: () -> Unit): Throwable? { try { block() diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt similarity index 86% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt index 54aaae0ca5..6e6c50818b 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectArrayChannelTest.kt @@ -19,13 +19,13 @@ package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.intrinsics.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class SelectArrayChannelTest : TestBase() { + @Test - fun testSelectSendSuccess() = runBlocking { + fun testSelectSendSuccess() = runTest { expect(1) val channel = ArrayChannel(1) launch(coroutineContext) { @@ -44,7 +44,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectSendSuccessWithDefault() = runBlocking { + fun testSelectSendSuccessWithDefault() = runTest { expect(1) val channel = ArrayChannel(1) launch(coroutineContext) { @@ -66,7 +66,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectSendReceiveBuf() = runBlocking { + fun testSelectSendReceiveBuf() = runTest { expect(1) val channel = ArrayChannel(1) select { @@ -85,7 +85,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectSendWait() = runBlocking { + fun testSelectSendWait() = runTest { expect(1) val channel = ArrayChannel(1) launch(coroutineContext) { @@ -107,7 +107,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectReceiveSuccess() = runBlocking { + fun testSelectReceiveSuccess() = runTest { expect(1) val channel = ArrayChannel(1) channel.send("OK") @@ -122,7 +122,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectReceiveSuccessWithDefault() = runBlocking { + fun testSelectReceiveSuccessWithDefault() = runTest { expect(1) val channel = ArrayChannel(1) channel.send("OK") @@ -140,7 +140,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectReceiveWaitWithDefault() = runBlocking { + fun testSelectReceiveWaitWithDefault() = runTest { expect(1) val channel = ArrayChannel(1) select { @@ -170,7 +170,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectReceiveWait() = runBlocking { + fun testSelectReceiveWait() = runTest { expect(1) val channel = ArrayChannel(1) launch(coroutineContext) { @@ -188,8 +188,8 @@ class SelectArrayChannelTest : TestBase() { finish(6) } - @Test(expected = ClosedReceiveChannelException::class) - fun testSelectReceiveClosed() = runBlocking { + @Test + fun testSelectReceiveClosed() = runTest({it is ClosedReceiveChannelException}) { expect(1) val channel = ArrayChannel(1) channel.close() @@ -202,8 +202,8 @@ class SelectArrayChannelTest : TestBase() { expectUnreached() } - @Test(expected = ClosedReceiveChannelException::class) - fun testSelectReceiveWaitClosed() = runBlocking { + @Test + fun testSelectReceiveWaitClosed() = runTest({it is ClosedReceiveChannelException}) { expect(1) val channel = ArrayChannel(1) launch(coroutineContext) { @@ -221,9 +221,9 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectSendResourceCleanup() = runBlocking { + fun testSelectSendResourceCleanup() = runTest { val channel = ArrayChannel(1) - val n = 10_000_000 * stressTestMultiplier + val n = 1000 expect(1) channel.send(-1) // fill the buffer, so all subsequent sends cannot proceed repeat(n) { i -> @@ -236,9 +236,9 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectReceiveResourceCleanup() = runBlocking { + fun testSelectReceiveResourceCleanup() = runTest { val channel = ArrayChannel(1) - val n = 10_000_000 * stressTestMultiplier + val n = 1000 expect(1) repeat(n) { i -> select { @@ -250,7 +250,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectReceiveDispatchNonSuspending() = runBlocking { + fun testSelectReceiveDispatchNonSuspending() = runTest { val channel = ArrayChannel(1) expect(1) channel.send(42) @@ -272,7 +272,7 @@ class SelectArrayChannelTest : TestBase() { } @Test - fun testSelectReceiveDispatchNonSuspending2() = runBlocking { + fun testSelectReceiveDispatchNonSuspending2() = runTest { val channel = ArrayChannel(1) expect(1) channel.send(42) @@ -303,4 +303,4 @@ class SelectArrayChannelTest : TestBase() { if (!trySelect(null)) return block.startCoroutineUndispatched(this) } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt similarity index 90% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt index eee7283897..20322cde1f 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt @@ -17,15 +17,14 @@ package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* -class SelectBiasTest { +class SelectBiasTest : TestBase() { val n = 10_000 @Test - fun testBiased() = runBlocking { + fun testBiased() = runTest { val d0 = async(coroutineContext) { 0 } val d1 = async(coroutineContext) { 1 } val counter = IntArray(2) @@ -41,7 +40,7 @@ class SelectBiasTest { } @Test - fun testUnbiased() = runBlocking { + fun testUnbiased() = runTest { val d0 = async(coroutineContext) { 0 } val d1 = async(coroutineContext) { 1 } val counter = IntArray(2) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt similarity index 87% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt index d71be6ae2a..04cde2a449 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectRendezvousChannelTest.kt @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 package kotlinx.coroutines.experimental.selects import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.* import kotlinx.coroutines.experimental.intrinsics.* -import org.junit.* -import org.junit.Assert.* import kotlin.coroutines.experimental.* +import kotlin.test.* class SelectRendezvousChannelTest : TestBase() { + @Test - fun testSelectSendSuccess() = runBlocking { + fun testSelectSendSuccess() = runTest { expect(1) val channel = RendezvousChannel() launch(coroutineContext) { @@ -44,7 +45,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectSendSuccessWithDefault() = runBlocking { + fun testSelectSendSuccessWithDefault() = runTest { expect(1) val channel = RendezvousChannel() launch(coroutineContext) { @@ -66,7 +67,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectSendWaitWithDefault() = runBlocking { + fun testSelectSendWaitWithDefault() = runTest { expect(1) val channel = RendezvousChannel() select { @@ -92,7 +93,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectSendWait() = runBlocking { + fun testSelectSendWait() = runTest { expect(1) val channel = RendezvousChannel() launch(coroutineContext) { @@ -110,7 +111,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectReceiveSuccess() = runBlocking { + fun testSelectReceiveSuccess() = runTest { expect(1) val channel = RendezvousChannel() launch(coroutineContext) { @@ -130,7 +131,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectReceiveSuccessWithDefault() = runBlocking { + fun testSelectReceiveSuccessWithDefault() = runTest { expect(1) val channel = RendezvousChannel() launch(coroutineContext) { @@ -153,7 +154,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectReceiveWaitWithDefault() = runBlocking { + fun testSelectReceiveWaitWithDefault() = runTest { expect(1) val channel = RendezvousChannel() select { @@ -179,7 +180,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectReceiveWait() = runBlocking { + fun testSelectReceiveWait() = runTest { expect(1) val channel = RendezvousChannel() launch(coroutineContext) { @@ -197,8 +198,8 @@ class SelectRendezvousChannelTest : TestBase() { finish(6) } - @Test(expected = ClosedReceiveChannelException::class) - fun testSelectReceiveClosed() = runBlocking { + @Test + fun testSelectReceiveClosed() = runTest(expected = { it is ClosedReceiveChannelException }) { expect(1) val channel = RendezvousChannel() channel.close() @@ -211,8 +212,8 @@ class SelectRendezvousChannelTest : TestBase() { expectUnreached() } - @Test(expected = ClosedReceiveChannelException::class) - fun testSelectReceiveWaitClosed() = runBlocking { + @Test + fun testSelectReceiveWaitClosed() = runTest(expected = {it is ClosedReceiveChannelException}) { expect(1) val channel = RendezvousChannel() launch(coroutineContext) { @@ -230,9 +231,9 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectSendResourceCleanup() = runBlocking { + fun testSelectSendResourceCleanup() = runTest { val channel = RendezvousChannel() - val n = 10_000_000 * stressTestMultiplier + val n = 1_000 expect(1) repeat(n) { i -> select { @@ -244,9 +245,9 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectReceiveResourceCleanup() = runBlocking { + fun testSelectReceiveResourceCleanup() = runTest { val channel = RendezvousChannel() - val n = 10_000_000 * stressTestMultiplier + val n = 1_000 expect(1) repeat(n) { i -> select { @@ -258,7 +259,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectAtomicFailure() = runBlocking { + fun testSelectAtomicFailure() = runTest { val c1 = RendezvousChannel() val c2 = RendezvousChannel() expect(1) @@ -289,7 +290,7 @@ class SelectRendezvousChannelTest : TestBase() { } @Test - fun testSelectWaitDispatch() = runBlocking { + fun testSelectWaitDispatch() = runTest { val c = RendezvousChannel() expect(1) launch(coroutineContext) { @@ -323,4 +324,4 @@ class SelectRendezvousChannelTest : TestBase() { if (!trySelect(null)) return block.startCoroutineUndispatched(this) } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectChannelStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectChannelStressTest.kt new file mode 100644 index 0000000000..097fe85a39 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectChannelStressTest.kt @@ -0,0 +1,72 @@ +package kotlinx.coroutines.experimental.selects + +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.channels.* +import kotlinx.coroutines.experimental.intrinsics.* +import kotlin.test.* + +class SelectChannelStressTest: TestBase() { + + @Test + fun testSelectSendResourceCleanupArrayChannel() = runTest { + val channel = ArrayChannel(1) + val n = 10_000_000 * stressTestMultiplier + expect(1) + channel.send(-1) // fill the buffer, so all subsequent sends cannot proceed + repeat(n) { i -> + select { + channel.onSend(i) { expectUnreached() } + default { expect(i + 2) } + } + } + finish(n + 2) + } + + @Test + fun testSelectReceiveResourceCleanupArrayChannel() = runTest { + val channel = ArrayChannel(1) + val n = 10_000_000 * stressTestMultiplier + expect(1) + repeat(n) { i -> + select { + channel.onReceive { expectUnreached() } + default { expect(i + 2) } + } + } + finish(n + 2) + } + + @Test + fun testSelectSendResourceCleanupRendezvousChannel() = runTest { + val channel = RendezvousChannel() + val n = 1_000_000 * stressTestMultiplier + expect(1) + repeat(n) { i -> + select { + channel.onSend(i) { expectUnreached() } + default { expect(i + 2) } + } + } + finish(n + 2) + } + + @Test + fun testSelectReceiveResourceRendezvousChannel() = runTest { + val channel = RendezvousChannel() + val n = 1_000_000 * stressTestMultiplier + expect(1) + repeat(n) { i -> + select { + channel.onReceive { expectUnreached() } + default { expect(i + 2) } + } + } + finish(n + 2) + } + + internal fun SelectBuilder.default(block: suspend () -> R) { + this as SelectBuilderImpl // type assertion + if (!trySelect(null)) return + block.startCoroutineUndispatched(this) + } +} diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt index 2569249418..1980ff5a5f 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt @@ -16,8 +16,6 @@ package kotlinx.coroutines.experimental.internal -import kotlinx.coroutines.experimental.channels.* - private typealias Node = LinkedListNode /** @suppress **This is unstable API and it is subject to change.** */ @@ -121,11 +119,14 @@ public actual open class RemoveFirstDesc actual constructor( @Suppress("UNCHECKED_CAST") public actual val result: T get() = affectedNode as T - protected override val affectedNode: Node get() = queue._prev - + protected override val affectedNode: Node = queue.next as Node protected actual open fun validatePrepared(node: T): Boolean = true - protected actual final override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? = null - protected override fun onComplete(): Unit { queue.removeFirstOrNull() } + protected actual final override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? { + @Suppress("UNCHECKED_CAST") + validatePrepared(affectedNode as T) + return null + } + protected override fun onComplete() { queue.removeFirstOrNull() } protected actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit } @@ -134,10 +135,17 @@ public actual abstract class AbstractAtomicDesc : AtomicDesc() { protected abstract val affectedNode: Node protected actual abstract fun onPrepare(affected: Node, next: Node): Any? protected abstract fun onComplete() - actual final override fun prepare(op: AtomicOp<*>): Any? = onPrepare(affectedNode, affectedNode._next) - actual final override fun complete(op: AtomicOp<*>, failure: Any?) = onComplete() - protected actual open fun failure(affected: LockFreeLinkedListNode, next: Any): Any? = null // Never fails + actual final override fun prepare(op: AtomicOp<*>): Any? { + val affected = affectedNode + val next = affected._next + val failure = failure(affected, next) + if (failure != null) return failure + return onPrepare(affected, next) + } + + actual final override fun complete(op: AtomicOp<*>, failure: Any?) = onComplete() + protected actual open fun failure(affected: LockFreeLinkedListNode, next: Any): Any? = null // Never fails by default protected actual open fun retry(affected: LockFreeLinkedListNode, next: Any): Boolean = false // Always succeeds protected actual abstract fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) } From 8a071688895de0bf06859b248ccd388e1897bd7d Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Sun, 22 Apr 2018 18:47:40 +0300 Subject: [PATCH 50/61] Update readme for JS channels --- js/kotlinx-coroutines-core-js/README.md | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/js/kotlinx-coroutines-core-js/README.md b/js/kotlinx-coroutines-core-js/README.md index 21ed982ad9..552d4e0502 100644 --- a/js/kotlinx-coroutines-core-js/README.md +++ b/js/kotlinx-coroutines-core-js/README.md @@ -8,6 +8,8 @@ Coroutine builder functions: | ------------- | ------------- | ---------------- | --------------- | [launch] | [Job] | [CoroutineScope] | Launches coroutine that does not have any result | [async] | [Deferred] | [CoroutineScope] | Returns a single value with the future result +| [produce][kotlinx.coroutines.experimental.channels.produce] | [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [ProducerScope][kotlinx.coroutines.experimental.channels.ProducerScope] | Produces a stream of elements + Coroutine dispatchers implementing [CoroutineDispatcher]: @@ -23,6 +25,14 @@ More context elements: | [NonCancellable] | A non-cancelable job that is always active | [CoroutineExceptionHandler] | Handler for uncaught exception +Synchronization primitives for coroutines: + +| **Name** | **Suspending functions** | **Description** +| ---------- | ----------------------------------------------------------- | --------------- +| [Mutex][kotlinx.coroutines.experimental.sync.Mutex] | [lock][kotlinx.coroutines.experimental.sync.Mutex.lock] | Mutual exclusion +| [Channel][kotlinx.coroutines.experimental.channels.Channel] | [send][kotlinx.coroutines.experimental.channels.SendChannel.send], [receive][kotlinx.coroutines.experimental.channels.ReceiveChannel.receive] | Communication channel (aka queue or exchanger) + + Top-level suspending functions: | **Name** | **Description** @@ -37,6 +47,19 @@ Cancellation support for user-defined suspending functions is available with [su helper function. [NonCancellable] job object is provided to suppress cancellation with `run(NonCancellable) {...}` block of code. +[Select][kotlinx.coroutines.experimental.selects.select] expression waits for the result of multiple suspending functions simultaneously: + +| **Receiver** | **Suspending function** | **Select clause** | **Non-suspending version** +| ---------------- | --------------------------------------------- | ------------------------------------------------ | -------------------------- +| [Job] | [join][Job.join] | [onJoin][Job.onJoin] | [isCompleted][Job.isCompleted] +| [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted] +| [SendChannel][kotlinx.coroutines.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 + + # Package kotlinx.coroutines.experimental General-purpose coroutine builders, contexts, and helper functions. @@ -59,4 +82,31 @@ General-purpose coroutine builders, contexts, and helper functions. [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 [suspendCancellableCoroutine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/suspend-cancellable-coroutine.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 + +[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 + +[kotlinx.coroutines.experimental.channels.produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.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.ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-producer-scope/index.html +[kotlinx.coroutines.experimental.channels.Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-channel/index.html +[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.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 + +[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.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-timeout.html From 11d6b5bc55c953d682b271c022bad8f5f3e0202e Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Thu, 26 Apr 2018 10:11:50 +0300 Subject: [PATCH 51/61] Hide declarations in internal package --- .../experimental/channels/AbstractChannel.kt | 16 ++++++++-------- .../experimental/internal/ArrayCopy.common.kt | 2 +- .../experimental/internal/Closeable.common.kt | 8 +++++++- .../experimental/internal/Concurrent.common.kt | 8 ++++---- .../internal/LockFreeLinkedList.common.kt | 1 + .../coroutines/experimental/internal/Symbol.kt | 2 +- .../experimental/internal/ArrayCopy.kt | 2 +- .../experimental/internal/Closeable.kt | 7 ++++++- .../experimental/internal/Concurrent.kt | 7 ++++--- .../experimental/internal/ArrayCopy.kt | 3 +-- .../experimental/internal/Closeable.kt | 9 +++++++-- .../experimental/internal/Concurrent.kt | 8 ++++---- .../experimental/internal/LinkedList.kt | 1 + 13 files changed, 46 insertions(+), 28 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt index 5fcfc87bf2..697c6ef5c5 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt @@ -935,28 +935,28 @@ public abstract class AbstractChannel : AbstractSendChannel(), Channel } /** @suppress **This is unstable API and it is subject to change.** */ -@JvmField val OFFER_SUCCESS: Any = Symbol("OFFER_SUCCESS") +@JvmField internal val OFFER_SUCCESS: Any = Symbol("OFFER_SUCCESS") /** @suppress **This is unstable API and it is subject to change.** */ -@JvmField val OFFER_FAILED: Any = Symbol("OFFER_FAILED") +@JvmField internal val OFFER_FAILED: Any = Symbol("OFFER_FAILED") /** @suppress **This is unstable API and it is subject to change.** */ -@JvmField val POLL_FAILED: Any = Symbol("POLL_FAILED") +@JvmField internal val POLL_FAILED: Any = Symbol("POLL_FAILED") /** @suppress **This is unstable API and it is subject to change.** */ -@JvmField val ENQUEUE_FAILED: Any = Symbol("ENQUEUE_FAILED") +@JvmField internal val ENQUEUE_FAILED: Any = Symbol("ENQUEUE_FAILED") /** @suppress **This is unstable API and it is subject to change.** */ -@JvmField val SELECT_STARTED: Any = Symbol("SELECT_STARTED") +@JvmField internal val SELECT_STARTED: Any = Symbol("SELECT_STARTED") /** @suppress **This is unstable API and it is subject to change.** */ -@JvmField val NULL_VALUE: Any = Symbol("NULL_VALUE") +@JvmField internal val NULL_VALUE: Any = Symbol("NULL_VALUE") /** @suppress **This is unstable API and it is subject to change.** */ -@JvmField val CLOSE_RESUMED: Any = Symbol("CLOSE_RESUMED") +@JvmField internal val CLOSE_RESUMED: Any = Symbol("CLOSE_RESUMED") /** @suppress **This is unstable API and it is subject to change.** */ -@JvmField val SEND_RESUMED = Symbol("SEND_RESUMED") +@JvmField internal val SEND_RESUMED = Symbol("SEND_RESUMED") /** * Represents sending waiter in the queue. diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.common.kt index fe20163456..eac8bbfbc5 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.common.kt @@ -3,4 +3,4 @@ package kotlinx.coroutines.experimental.internal /** * Cross-platform array copy. Overlaps of source and destination are not supported */ -expect fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int) \ No newline at end of file +internal expect fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int) \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.common.kt index 2b1f504b2b..3004a10782 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.common.kt @@ -1,5 +1,11 @@ package kotlinx.coroutines.experimental.internal -expect interface Closeable { +/** + * Closeable entity. + * @suppress **Deprecated** + */ +@Deprecated("No replacement, see specific use") +public expect interface Closeable { + @Deprecated("No replacement, see specific code") fun close() } diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.common.kt index df36305a29..3141212b94 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.common.kt @@ -6,13 +6,13 @@ package kotlinx.coroutines.experimental.internal * * Note that this alias is intentionally not named as CopyOnWriteList to avoid accidental misusage outside of ArrayBroadcastChannel */ -typealias SubscribersList = MutableList +internal typealias SubscribersList = MutableList -expect fun subscriberList(): SubscribersList +internal expect fun subscriberList(): SubscribersList -expect class ReentrantLock() { +internal expect class ReentrantLock() { fun tryLock(): Boolean fun unlock(): Unit } -expect inline fun ReentrantLock.withLock(action: () -> T): T +internal expect inline fun ReentrantLock.withLock(action: () -> T): T diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt index e23fd988db..aab23d1cf0 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt @@ -60,6 +60,7 @@ public expect open class AddLastDesc( override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) } +/** @suppress **This is unstable API and it is subject to change.** */ public expect open class RemoveFirstDesc(queue: LockFreeLinkedListNode): AbstractAtomicDesc { val queue: LockFreeLinkedListNode public val result: T diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Symbol.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Symbol.kt index 530d9d7942..6f391e9fbd 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Symbol.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/Symbol.kt @@ -21,6 +21,6 @@ package kotlinx.coroutines.experimental.internal * * @suppress **This is unstable API and it is subject to change.** */ -public class Symbol(val symbol: String) { +internal class Symbol(val symbol: String) { override fun toString(): String = symbol } diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt index bb365527d0..5d3b74d7d6 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt @@ -1,5 +1,5 @@ package kotlinx.coroutines.experimental.internal -actual fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int){ +internal actual fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int){ System.arraycopy(source, srcPos, destination, destinationStart, length) } \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt index 9552fc2706..86a7bbbf59 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt @@ -1,3 +1,8 @@ package kotlinx.coroutines.experimental.internal -actual typealias Closeable = java.io.Closeable +/** + * Closeable entity. + * @suppress **Deprecated** + */ +@Deprecated("No replacement, see specific use") +public actual typealias Closeable = java.io.Closeable diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt index 1eef996481..0422d2b0c9 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt @@ -3,8 +3,9 @@ package kotlinx.coroutines.experimental.internal import java.util.concurrent.* import kotlin.concurrent.withLock as withLockJvm -actual fun subscriberList(): MutableList = CopyOnWriteArrayList() +internal actual fun subscriberList(): MutableList = CopyOnWriteArrayList() -actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock +@Suppress("ACTUAL_WITHOUT_EXPECT") +internal actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock -actual inline fun ReentrantLock.withLock(action: () -> T) = this.withLockJvm(action) +internal actual inline fun ReentrantLock.withLock(action: () -> T) = this.withLockJvm(action) diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt index 133cb542e1..1cab961929 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/ArrayCopy.kt @@ -1,7 +1,6 @@ package kotlinx.coroutines.experimental.internal - -actual fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int) { +internal actual fun arraycopy(source: Array, srcPos: Int, destination: Array, destinationStart: Int, length: Int) { var destinationIndex = destinationStart for (sourceIndex in srcPos until srcPos + length) { destination[destinationIndex++] = source[sourceIndex] diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt index 7888c8e3ba..938767ca43 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Closeable.kt @@ -1,5 +1,10 @@ package kotlinx.coroutines.experimental.internal -actual interface Closeable { - actual fun close() +/** + * Closeable entity. + * @suppress **Deprecated** + */ +@Deprecated("No replacement, see specific use") +public actual interface Closeable { + public actual fun close() } diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt index 20dcd6bb4c..76c1a33f77 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/Concurrent.kt @@ -1,12 +1,12 @@ package kotlinx.coroutines.experimental.internal -actual typealias ReentrantLock = NoOpLock +internal actual typealias ReentrantLock = NoOpLock -actual inline fun ReentrantLock.withLock(action: () -> T) = action() +internal actual inline fun ReentrantLock.withLock(action: () -> T) = action() -public class NoOpLock { +internal class NoOpLock { fun tryLock() = true fun unlock(): Unit {} } -actual fun subscriberList(): MutableList = ArrayList() +internal actual fun subscriberList(): MutableList = ArrayList() diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt index 1980ff5a5f..30c3f3c1d0 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt @@ -113,6 +113,7 @@ public actual open class AddLastDesc actual constructor( protected actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit } +/** @suppress **This is unstable API and it is subject to change.** */ public actual open class RemoveFirstDesc actual constructor( actual val queue: LockFreeLinkedListNode ) : AbstractAtomicDesc() { From 769d7dcb6584e4fc7dec6ed73e97cf37c6870ac1 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Thu, 26 Apr 2018 12:13:40 +0300 Subject: [PATCH 52/61] Moved awaitAll/joinAll to common module Fixes #171 --- .../kotlinx/coroutines/experimental/Await.kt | 20 +- .../coroutines/experimental/AwaitTest.kt | 203 ++++++++++-------- core/kotlinx-coroutines-core/README.md | 10 +- .../experimental/AwaitStressTest.kt | 1 - js/kotlinx-coroutines-core-js/README.md | 21 +- 5 files changed, 153 insertions(+), 102 deletions(-) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt (84%) rename {core/kotlinx-coroutines-core => common/kotlinx-coroutines-core-common}/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt (63%) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt similarity index 84% rename from core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt rename to common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt index d39d300bd9..59285b3028 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -1,3 +1,19 @@ +/* + * 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 @@ -56,11 +72,11 @@ private class AwaitAll(private val deferreds: Array>) { suspend fun await(): List = suspendCancellableCoroutine { cont -> deferreds.forEach { it.start() // To properly await lazily started deferreds - cont.disposeOnCompletion(it.invokeOnCompletion(AwaitAllNode(cont, it))) + cont.disposeOnCompletion(it.invokeOnCompletion(AwaitAllNode(cont, it).asHandler)) } } - inner class AwaitAllNode(private val continuation: CancellableContinuation>, job: Job) : JobNode(job), CompletionHandler { + inner class AwaitAllNode(private val continuation: CancellableContinuation>, job: Job) : JobNode(job) { override fun invoke(cause: Throwable?) { if (cause != null) { val token = continuation.tryResumeWithException(cause) diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt similarity index 63% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt rename to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt index 873b4080f2..34d6814c16 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt @@ -1,3 +1,19 @@ +/* + * 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.coroutineContext @@ -33,8 +49,14 @@ class AwaitTest : TestBase() { @Test fun testAwaitAllLazy() = runTest { expect(1) - val d = async(coroutineContext, start = CoroutineStart.LAZY) { expect(2); 1 } - val d2 = async(coroutineContext, start = CoroutineStart.LAZY) { expect(3); 2 } + val d = async( + coroutineContext, + start = CoroutineStart.LAZY + ) { expect(2); 1 } + val d2 = async( + coroutineContext, + start = CoroutineStart.LAZY + ) { expect(3); 2 } assertEquals(listOf(1, 2), awaitAll(d, d2)) finish(4) } @@ -43,44 +65,45 @@ class AwaitTest : TestBase() { fun testAwaitAllTyped() = runTest { val d1 = async(coroutineContext) { 1L } val d2 = async(coroutineContext) { "" } - val d3 = async(coroutineContext) { } + val d3 = async(coroutineContext) { } assertEquals(listOf(1L, ""), listOf(d1, d2).awaitAll()) assertEquals(listOf(1L, Unit), listOf(d1, d3).awaitAll()) assertEquals(listOf("", Unit), listOf(d2, d3).awaitAll()) } - @Test - fun testAwaitAllExceptionally() = runTest { - expect(1) - val d = async(coroutineContext) { - expect(3) - "OK" - } - - val d2 = async(coroutineContext) { - yield() - throw TestException() - } - - val d3 = async(coroutineContext) { - expect(4) - delay(Long.MAX_VALUE) - 1 - } - - expect(2) - try { - awaitAll(d, d2, d3) - } catch (e: TestException) { - expect(5) - } - - yield() - require(d.isCompleted && d2.isCompletedExceptionally && d3.isActive) - d3.cancel() - finish(6) - } +// todo: HANGS ON JS +// @Test +// fun testAwaitAllExceptionally() = runTest { +// expect(1) +// val d = async(coroutineContext) { +// expect(3) +// "OK" +// } +// +// val d2 = async(coroutineContext) { +// yield() +// throw TestException() +// } +// +// val d3 = async(coroutineContext) { +// expect(4) +// delay(Long.MAX_VALUE) +// 1 +// } +// +// expect(2) +// try { +// awaitAll(d, d2, d3) +// } catch (e: TestException) { +// expect(5) +// } +// +// yield() +// require(d.isCompleted && d2.isCompletedExceptionally && d3.isActive) +// d3.cancel() +// finish(6) +// } @Test fun testAwaitAllMultipleExceptions() = runTest { @@ -108,29 +131,30 @@ class AwaitTest : TestBase() { finish(4) } - @Test - fun testAwaitAllCancellation() = runTest { - val outer = async(coroutineContext) { - - expect(1) - val inner = async(coroutineContext) { - expect(4) - delay(Long.MAX_VALUE) - } - - expect(2) - awaitAll(inner) - expectUnreached() - } - - yield() - expect(3) - yield() - require(outer.isActive) - outer.cancel() - require(outer.isCancelled) - finish(5) - } +// todo: HANGS ON JS +// @Test +// fun testAwaitAllCancellation() = runTest { +// val outer = async(coroutineContext) { +// +// expect(1) +// val inner = async(coroutineContext) { +// expect(4) +// delay(Long.MAX_VALUE) +// } +// +// expect(2) +// awaitAll(inner) +// expectUnreached() +// } +// +// yield() +// expect(3) +// yield() +// require(outer.isActive) +// outer.cancel() +// require(outer.isCancelled) +// finish(5) +// } @Test fun testAwaitAllPartiallyCompleted() = runTest { @@ -193,8 +217,10 @@ class AwaitTest : TestBase() { @Test fun testAwaitAllFullyCompletedExceptionally() = runTest { - val d1 = CompletableDeferred(parent = null).apply { completeExceptionally(TestException()) } - val d2 = CompletableDeferred(parent = null).apply { completeExceptionally(TestException()) } + val d1 = CompletableDeferred(parent = null) + .apply { completeExceptionally(TestException()) } + val d2 = CompletableDeferred(parent = null) + .apply { completeExceptionally(TestException()) } val job = async(coroutineContext) { expect(3) } expect(1) try { @@ -216,7 +242,8 @@ class AwaitTest : TestBase() { @Test fun testAwaitAllSameThrowingJobMultipleTimes() = runTest { - val d1 = async(coroutineContext) { throw TestException() } + val d1 = + async(coroutineContext) { throw TestException() } val d2 = async(coroutineContext) { } // do nothing try { @@ -256,8 +283,14 @@ class AwaitTest : TestBase() { @Test fun testJoinAllLazy() = runTest { expect(1) - val d = async(coroutineContext, start = CoroutineStart.LAZY) { expect(2) } - val d2 = launch(coroutineContext, start = CoroutineStart.LAZY) { expect(3) } + val d = async( + coroutineContext, + start = CoroutineStart.LAZY + ) { expect(2) } + val d2 = launch( + coroutineContext, + start = CoroutineStart.LAZY + ) { expect(3) } joinAll(d, d2) finish(4) } @@ -280,27 +313,28 @@ class AwaitTest : TestBase() { finish(5) } - @Test - fun testJoinAllCancellation() = runTest { - val outer = launch(coroutineContext) { - expect(2) - val inner = launch(coroutineContext) { - expect(3) - delay(Long.MAX_VALUE) - } - - joinAll(inner) - expectUnreached() - } - - expect(1) - yield() - require(outer.isActive) - yield() - outer.cancel() - outer.join() - finish(4) - } +// todo: HANGS ON JS +// @Test +// fun testJoinAllCancellation() = runTest { +// val outer = launch(coroutineContext) { +// expect(2) +// val inner = launch(coroutineContext) { +// expect(3) +// delay(Long.MAX_VALUE) +// } +// +// joinAll(inner) +// expectUnreached() +// } +// +// expect(1) +// yield() +// require(outer.isActive) +// yield() +// outer.cancel() +// outer.join() +// finish(4) +// } @Test fun testJoinAllAlreadyCompleted() = runTest { @@ -331,7 +365,8 @@ class AwaitTest : TestBase() { @Test fun testJoinAllSameJobExceptionally() = runTest { - val job = async(coroutineContext) { throw TestException() } + val job = + async(coroutineContext) { throw TestException() } joinAll(job, job, job) } diff --git a/core/kotlinx-coroutines-core/README.md b/core/kotlinx-coroutines-core/README.md index 2d21725022..c32be5029d 100644 --- a/core/kotlinx-coroutines-core/README.md +++ b/core/kotlinx-coroutines-core/README.md @@ -49,6 +49,10 @@ Top-level suspending functions: | [awaitAll] | Awaits for successful completion of all given jobs or exceptional completion of any | [joinAll] | Joins on all given jobs +Cancellation support for user-defined suspending functions is available with [suspendCancellableCoroutine] +helper function. [NonCancellable] job object is provided to suppress cancellation with +`run(NonCancellable) {...}` block of code. + [Select][kotlinx.coroutines.experimental.selects.select] expression waits for the result of multiple suspending functions simultaneously: | **Receiver** | **Suspending function** | **Select clause** | **Non-suspending version** @@ -61,10 +65,6 @@ Top-level suspending functions: | [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] -helper function. [NonCancellable] job object is provided to suppress cancellation with -`run(NonCancellable) {...}` block of code. - This module provides debugging facilities for coroutines (run JVM with `-ea` or `-Dkotlinx.coroutines.debug` options) and [newCoroutineContext] function to write user-defined coroutine builders that work with these debugging facilities. @@ -117,12 +117,12 @@ Optional time unit support for multiplatform projects. [withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout-or-null.html [awaitAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/await-all.html [joinAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/join-all.html +[suspendCancellableCoroutine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/suspend-cancellable-coroutine.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 [kotlinx.coroutines.experimental.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/index.html diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt index 95e6bf8c63..d5be824e4e 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitStressTest.kt @@ -3,7 +3,6 @@ package kotlinx.coroutines.experimental import org.junit.* import org.junit.Test import java.util.concurrent.* -import kotlin.test.* class AwaitStressTest : TestBase() { diff --git a/js/kotlinx-coroutines-core-js/README.md b/js/kotlinx-coroutines-core-js/README.md index 552d4e0502..4933712719 100644 --- a/js/kotlinx-coroutines-core-js/README.md +++ b/js/kotlinx-coroutines-core-js/README.md @@ -10,7 +10,6 @@ Coroutine builder functions: | [async] | [Deferred] | [CoroutineScope] | Returns a single value with the future result | [produce][kotlinx.coroutines.experimental.channels.produce] | [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [ProducerScope][kotlinx.coroutines.experimental.channels.ProducerScope] | Produces a stream of elements - Coroutine dispatchers implementing [CoroutineDispatcher]: | **Name** | **Description** @@ -32,16 +31,17 @@ Synchronization primitives for coroutines: | [Mutex][kotlinx.coroutines.experimental.sync.Mutex] | [lock][kotlinx.coroutines.experimental.sync.Mutex.lock] | Mutual exclusion | [Channel][kotlinx.coroutines.experimental.channels.Channel] | [send][kotlinx.coroutines.experimental.channels.SendChannel.send], [receive][kotlinx.coroutines.experimental.channels.ReceiveChannel.receive] | Communication channel (aka queue or exchanger) - Top-level suspending functions: -| **Name** | **Description** -| ------------------- | --------------- -| [delay] | Non-blocking sleep -| [yield] | Yields thread in single-threaded dispatchers -| [withContext] | Switches to a different context -| [withTimeout] | Set execution time-limit with exception on timeout -| [withTimeoutOrNull] | Set execution time-limit will null result on timeout +| **Name** | **Description** +| ------------------- | --------------- +| [delay] | Non-blocking sleep +| [yield] | Yields thread in single-threaded dispatchers +| [withContext] | Switches to a different context +| [withTimeout] | Set execution time-limit with exception on timeout +| [withTimeoutOrNull] | Set execution time-limit will null result on timeout +| [awaitAll] | Awaits for successful completion of all given jobs or exceptional completion of any +| [joinAll] | Joins on all given jobs Cancellation support for user-defined suspending functions is available with [suspendCancellableCoroutine] helper function. [NonCancellable] job object is provided to suppress cancellation with @@ -59,7 +59,6 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio | [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 - # Package kotlinx.coroutines.experimental General-purpose coroutine builders, contexts, and helper functions. @@ -81,6 +80,8 @@ General-purpose coroutine builders, contexts, and helper functions. [withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-context.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 +[awaitAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/await-all.html +[joinAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/join-all.html [suspendCancellableCoroutine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/suspend-cancellable-coroutine.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 From 0406a9b40ee95077f4e3eac05ee72d5b1368c799 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Thu, 26 Apr 2018 12:35:43 +0300 Subject: [PATCH 53/61] Remove prev/next from common LockFreeLinkedList API (using nextNode/prevNode only) --- .../experimental/channels/AbstractChannel.kt | 16 ++++++++-------- .../internal/LockFreeLinkedList.common.kt | 2 -- .../experimental/internal/LockFreeLinkedList.kt | 12 +++++++----- .../experimental/internal/LinkedList.kt | 5 +---- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt index 697c6ef5c5..aaa6aa226c 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt @@ -83,13 +83,13 @@ public abstract class AbstractSendChannel : SendChannel { * Returns non-null closed token if it is last in the queue. * @suppress **This is unstable API and it is subject to change.** */ - protected val closedForSend: Closed<*>? get() = queue.prev as? Closed<*> + protected val closedForSend: Closed<*>? get() = queue.prevNode as? Closed<*> /** * Returns non-null closed token if it is first in the queue. * @suppress **This is unstable API and it is subject to change.** */ - protected val closedForReceive: Closed<*>? get() = queue.next as? Closed<*> + protected val closedForReceive: Closed<*>? get() = queue.nextNode as? Closed<*> /** * Retrieves first sending waiter from the queue or returns closed token. @@ -130,7 +130,7 @@ public abstract class AbstractSendChannel : SendChannel { * @suppress **This is unstable API and it is subject to change.** */ protected fun conflatePreviousSendBuffered(node: LockFreeLinkedListNode) { - val prev = node.prev + val prev = node.prevNode (prev as? SendBuffered<*>)?.remove() } @@ -168,7 +168,7 @@ public abstract class AbstractSendChannel : SendChannel { // ------ SendChannel ------ public final override val isClosedForSend: Boolean get() = closedForSend != null - public final override val isFull: Boolean get() = queue.next !is ReceiveOrClosed<*> && isBufferFull + public final override val isFull: Boolean get() = queue.nextNode !is ReceiveOrClosed<*> && isBufferFull public final override suspend fun send(element: E) { // fast path -- try offer non-blocking @@ -376,7 +376,7 @@ public abstract class AbstractSendChannel : SendChannel { private val queueDebugStateString: String get() { - val head = queue.next + val head = queue.nextNode if (head === queue) return "EmptyQueue" var result = when (head) { is Closed<*> -> head.toString() @@ -384,7 +384,7 @@ public abstract class AbstractSendChannel : SendChannel { is Send -> "SendQueued" else -> "UNEXPECTED:$head" // should not happen } - val tail = queue.prev + val tail = queue.prevNode if (tail !== head) { result += ",queueSize=${countQueueSize()}" if (tail is Closed<*>) result += ",closedForSend=$tail" @@ -498,12 +498,12 @@ public abstract class AbstractChannel : AbstractSendChannel(), Channel /** * @suppress **This is unstable API and it is subject to change.** */ - protected val hasReceiveOrClosed: Boolean get() = queue.next is ReceiveOrClosed<*> + protected val hasReceiveOrClosed: Boolean get() = queue.nextNode is ReceiveOrClosed<*> // ------ ReceiveChannel ------ public final override val isClosedForReceive: Boolean get() = closedForReceive != null && isBufferEmpty - public final override val isEmpty: Boolean get() = queue.next !is Send && isBufferEmpty + public final override val isEmpty: Boolean get() = queue.nextNode !is Send && isBufferEmpty @Suppress("UNCHECKED_CAST") public final override suspend fun receive(): E { diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt index aab23d1cf0..cd87ede7e3 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.common.kt @@ -19,9 +19,7 @@ package kotlinx.coroutines.experimental.internal /** @suppress **This is unstable API and it is subject to change.** */ public expect open class LockFreeLinkedListNode() { public val isRemoved: Boolean - public val next: Any public val nextNode: LockFreeLinkedListNode - public val prev: Any public val prevNode: LockFreeLinkedListNode public fun addLast(node: LockFreeLinkedListNode) public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean diff --git a/core/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 index b99b3fcb41..15166a43ab 100644 --- a/core/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 @@ -101,17 +101,18 @@ public actual open class LockFreeLinkedListNode { public actual val isRemoved: Boolean get() = next is Removed // LINEARIZABLE. Returns Node | Removed - public actual val next: Any get() { + public val next: Any get() { _next.loop { next -> if (next !is OpDescriptor) return next next.perform(this) } } + // LINEARIZABLE. Returns next non-removed Node public actual val nextNode: Node get() = next.unwrap() // LINEARIZABLE. Returns Node | Removed - public actual val prev: Any get() { + public val prev: Any get() { _prev.loop { prev -> if (prev is Removed) return prev prev as Node // otherwise, it can be only node @@ -120,6 +121,7 @@ public actual open class LockFreeLinkedListNode { } } + // LINEARIZABLE. Returns prev non-removed Node public actual val prevNode: Node get() = prev.unwrap() // ------ addOneIfEmpty ------ @@ -565,7 +567,7 @@ public actual open class LockFreeLinkedListNode { var cur = this while (true) { if (cur is LockFreeLinkedListHead) return cur - cur = cur.next.unwrap() + cur = cur.nextNode check(cur !== this) { "Cannot loop to this while looking for list head" } } } @@ -679,7 +681,7 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { var cur: Node = next as Node while (cur != this) { if (cur is T) block(cur) - cur = cur.next.unwrap() + cur = cur.nextNode } } @@ -692,7 +694,7 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { var prev: Node = this var cur: Node = next as Node while (cur != this) { - val next = cur.next.unwrap() + val next = cur.nextNode cur.validateNode(prev, next) prev = cur cur = next diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt index 30c3f3c1d0..c4ead3fdd5 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/internal/LinkedList.kt @@ -31,9 +31,6 @@ public open class LinkedListNode { @PublishedApi internal var _prev = this @PublishedApi internal var _removed: Boolean = false - public val prev: Any get() = _prev - public val next: Any get() = _next - public inline val nextNode get() = _next public inline val prevNode get() = _prev public inline val isRemoved get() = _removed @@ -120,7 +117,7 @@ public actual open class RemoveFirstDesc actual constructor( @Suppress("UNCHECKED_CAST") public actual val result: T get() = affectedNode as T - protected override val affectedNode: Node = queue.next as Node + protected override val affectedNode: Node = queue.nextNode protected actual open fun validatePrepared(node: T): Boolean = true protected actual final override fun onPrepare(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode): Any? { @Suppress("UNCHECKED_CAST") From cd00643b92c9698ca728f92a66f52b30b3811d05 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 26 Apr 2018 16:03:40 +0300 Subject: [PATCH 54/61] Delay bug fixes: Invoke clearTimeout on cancellation in JSDispatcher Remove delayed task on cancellation from ThreadSafeHeap on cancellation to avoid memory leak --- .../coroutines/experimental/AwaitTest.kt | 153 +++++++++--------- .../coroutines/experimental/DelayTest.kt | 61 +++++++ .../coroutines/experimental/EventLoop.kt | 18 ++- .../experimental/DefaultExecutorStressTest.kt | 37 +++++ .../{DelayTest.kt => DelayJvmTest.kt} | 2 +- .../coroutines/experimental/JSDispatcher.kt | 4 +- 6 files changed, 194 insertions(+), 81 deletions(-) create mode 100644 common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DefaultExecutorStressTest.kt rename core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/{DelayTest.kt => DelayJvmTest.kt} (98%) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt index 34d6814c16..a63a825301 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/AwaitTest.kt @@ -72,38 +72,37 @@ class AwaitTest : TestBase() { assertEquals(listOf("", Unit), listOf(d2, d3).awaitAll()) } -// todo: HANGS ON JS -// @Test -// fun testAwaitAllExceptionally() = runTest { -// expect(1) -// val d = async(coroutineContext) { -// expect(3) -// "OK" -// } -// -// val d2 = async(coroutineContext) { -// yield() -// throw TestException() -// } -// -// val d3 = async(coroutineContext) { -// expect(4) -// delay(Long.MAX_VALUE) -// 1 -// } -// -// expect(2) -// try { -// awaitAll(d, d2, d3) -// } catch (e: TestException) { -// expect(5) -// } -// -// yield() -// require(d.isCompleted && d2.isCompletedExceptionally && d3.isActive) -// d3.cancel() -// finish(6) -// } + @Test + fun testAwaitAllExceptionally() = runTest { + expect(1) + val d = async(coroutineContext) { + expect(3) + "OK" + } + + val d2 = async(coroutineContext) { + yield() + throw TestException() + } + + val d3 = async(coroutineContext) { + expect(4) + delay(Long.MAX_VALUE) + 1 + } + + expect(2) + try { + awaitAll(d, d2, d3) + } catch (e: TestException) { + expect(5) + } + + yield() + require(d.isCompleted && d2.isCompletedExceptionally && d3.isActive) + d3.cancel() + finish(6) + } @Test fun testAwaitAllMultipleExceptions() = runTest { @@ -131,30 +130,29 @@ class AwaitTest : TestBase() { finish(4) } -// todo: HANGS ON JS -// @Test -// fun testAwaitAllCancellation() = runTest { -// val outer = async(coroutineContext) { -// -// expect(1) -// val inner = async(coroutineContext) { -// expect(4) -// delay(Long.MAX_VALUE) -// } -// -// expect(2) -// awaitAll(inner) -// expectUnreached() -// } -// -// yield() -// expect(3) -// yield() -// require(outer.isActive) -// outer.cancel() -// require(outer.isCancelled) -// finish(5) -// } + @Test + fun testAwaitAllCancellation() = runTest { + val outer = async(coroutineContext) { + + expect(1) + val inner = async(coroutineContext) { + expect(4) + delay(Long.MAX_VALUE) + } + + expect(2) + awaitAll(inner) + expectUnreached() + } + + yield() + expect(3) + yield() + require(outer.isActive) + outer.cancel() + require(outer.isCancelled) + finish(5) + } @Test fun testAwaitAllPartiallyCompleted() = runTest { @@ -313,28 +311,27 @@ class AwaitTest : TestBase() { finish(5) } -// todo: HANGS ON JS -// @Test -// fun testJoinAllCancellation() = runTest { -// val outer = launch(coroutineContext) { -// expect(2) -// val inner = launch(coroutineContext) { -// expect(3) -// delay(Long.MAX_VALUE) -// } -// -// joinAll(inner) -// expectUnreached() -// } -// -// expect(1) -// yield() -// require(outer.isActive) -// yield() -// outer.cancel() -// outer.join() -// finish(4) -// } + @Test + fun testJoinAllCancellation() = runTest { + val outer = launch(coroutineContext) { + expect(2) + val inner = launch(coroutineContext) { + expect(3) + delay(Long.MAX_VALUE) + } + + joinAll(inner) + expectUnreached() + } + + expect(1) + yield() + require(outer.isActive) + yield() + outer.cancel() + outer.join() + finish(4) + } @Test fun testJoinAllAlreadyCompleted() = runTest { diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt new file mode 100644 index 0000000000..9904d3d12c --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt @@ -0,0 +1,61 @@ + +@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 + +package kotlinx.coroutines.experimental + +import kotlinx.coroutines.experimental.timeunit.* +import kotlin.coroutines.experimental.* +import kotlin.test.* + +class DelayTest : TestBase() { + + @Test + fun testCancellation() = runTest(expected = {it is JobCancellationException}) { + runAndCancel(3600, TimeUnit.SECONDS) + } + + @Test + fun testMaxLongValue()= runTest(expected = {it is JobCancellationException}) { + runAndCancel(Long.MAX_VALUE) + } + + @Test + fun testMaxIntValue()= runTest(expected = {it is JobCancellationException}) { + runAndCancel(Int.MAX_VALUE.toLong()) + } + + @Test + fun testOverflowOnUnitConversion()= runTest(expected = {it is JobCancellationException}) { + runAndCancel(Long.MAX_VALUE, TimeUnit.SECONDS) + } + + @Test + fun testRegularDelay() = runTest { + val deferred = async(coroutineContext) { + expect(2) + delay(1) + expect(3) + } + + expect(1) + yield() + deferred.await() + finish(4) + } + + private suspend fun runAndCancel(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) { + expect(1) + val deferred = async(coroutineContext) { + expect(2) + delay(time, unit) + expectUnreached() + } + + yield() + expect(3) + require(deferred.isActive) + deferred.cancel() + finish(4) + deferred.await() + } +} diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt index 6d6685beff..3e7d942817 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt @@ -18,9 +18,10 @@ package kotlinx.coroutines.experimental import kotlinx.atomicfu.* import kotlinx.coroutines.experimental.internal.* -import kotlinx.coroutines.experimental.timeunit.TimeUnit +import kotlinx.coroutines.experimental.timeunit.* import java.util.concurrent.locks.* import kotlin.coroutines.experimental.* +import kotlin.jvm.* /** * Implemented by [CoroutineDispatcher] implementations that have event loop inside and can @@ -303,6 +304,12 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop { time: Long, timeUnit: TimeUnit, private val cont: CancellableContinuation ) : DelayedTask(time, timeUnit) { + + init { + // Note that this operation isn't lock-free, but very short + cont.invokeOnCompletion(DelayedCancellationHandler(cont, this)) + } + override fun run() { with(cont) { resumeUndispatched(Unit) } } @@ -315,6 +322,15 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop { override fun run() { block.run() } override fun toString(): String = super.toString() + block.toString() } + + private class DelayedCancellationHandler( + cont: CancellableContinuation, + private val task: DelayedResumeTask + ) : JobNode>(cont) { + override fun invoke(cause: Throwable?) { + task.dispose() + } + } } internal abstract class ThreadEventLoop( diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DefaultExecutorStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DefaultExecutorStressTest.kt new file mode 100644 index 0000000000..0291866175 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DefaultExecutorStressTest.kt @@ -0,0 +1,37 @@ +package kotlinx.coroutines.experimental + +import org.junit.* +import java.util.concurrent.* +import kotlin.coroutines.experimental.* + +class DefaultExecutorStressTest : TestBase() { + + @Test + fun testDelay() = runTest { + val iterations = 100_000 * stressTestMultiplier + + val ctx = DefaultExecutor + coroutineContext + expect(1) + var expected = 1 + repeat(iterations) { + expect(++expected) + val deferred = async(ctx) { + expect(++expected) + val largeArray = IntArray(10_000) { it } + delay(Long.MAX_VALUE, TimeUnit.NANOSECONDS) + println(largeArray) // consume to avoid DCE, actually unreachable + } + + expect(++expected) + yield() + deferred.cancel() + try { + deferred.await() + } catch (e: JobCancellationException) { + expect(++expected) + } + } + + finish(2 + iterations * 4) + } +} 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/DelayJvmTest.kt similarity index 98% rename from core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt rename to core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DelayJvmTest.kt index 764f73602f..c21d4f7298 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DelayTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/DelayJvmTest.kt @@ -23,7 +23,7 @@ import java.util.concurrent.Executor import java.util.concurrent.Executors import kotlin.coroutines.experimental.* -class DelayTest : TestBase() { +class DelayJvmTest : TestBase() { /** * Test that delay works properly in contexts with custom [ContinuationInterceptor] */ diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt index 10e072f975..080ac466a5 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt @@ -26,7 +26,9 @@ internal class NodeDispatcher : CoroutineDispatcher(), Delay { } override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation) { - setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) + val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) + // Actually on cancellation, but clearTimeout is idempotent + continuation.invokeOnCompletion { clearTimeout(handle) } } override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { From b4c7b40fa7c199d78177a07717a30fb83e454322 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Fri, 27 Apr 2018 11:57:56 +0300 Subject: [PATCH 55/61] Use disposeOnCompletion to remove cancelled delays --- .../kotlinx/coroutines/experimental/EventLoop.kt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt index 3e7d942817..2cb2433b4d 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt @@ -307,7 +307,7 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop { init { // Note that this operation isn't lock-free, but very short - cont.invokeOnCompletion(DelayedCancellationHandler(cont, this)) + cont.disposeOnCompletion(this) } override fun run() { @@ -322,15 +322,6 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop { override fun run() { block.run() } override fun toString(): String = super.toString() + block.toString() } - - private class DelayedCancellationHandler( - cont: CancellableContinuation, - private val task: DelayedResumeTask - ) : JobNode>(cont) { - override fun invoke(cause: Throwable?) { - task.dispose() - } - } } internal abstract class ThreadEventLoop( From dbd9e1c4828ac63a3e7bcf87434a7b5da2eced9e Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Sat, 28 Apr 2018 15:14:18 +0300 Subject: [PATCH 56/61] Consistent naming of handler base classes and their concrete impls --- .../experimental/AbstractContinuation.kt | 30 ++++-------- .../experimental/CancellableContinuation.kt | 46 ++++--------------- .../experimental/CompletionHandler.common.kt | 12 +++-- .../coroutines/experimental/JobSupport.kt | 2 +- .../experimental/CompletionHandler.kt | 10 ++-- .../kotlinx/coroutines/experimental/Future.kt | 22 +++------ .../experimental/CompletionHandler.kt | 18 +++++--- 7 files changed, 49 insertions(+), 91 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt index a416496164..66d843f1b6 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/AbstractContinuation.kt @@ -74,7 +74,7 @@ internal abstract class AbstractContinuation( name state class public state description ------ ------------ ------------ ----------- ACTIVE Active : Active active, no listeners - SINGLE_A CancellationHandler : Active active, one cancellation listener + SINGLE_A CancelHandler : Active active, one cancellation listener CANCELLING Cancelling : Active in the process of cancellation due to cancellation of parent job CANCELLED Cancelled : Cancelled cancelled (final state) COMPLETED any : Completed produced some result or threw an exception (final state) @@ -158,7 +158,7 @@ internal abstract class AbstractContinuation( resumeImpl(CompletedExceptionally(exception), resumeMode) public fun invokeOnCancellation(handler: CompletionHandler) { - var handleCache: CancellationHandler? = null + var handleCache: CancelHandler? = null loopOnState { state -> when (state) { is Active -> { @@ -167,7 +167,7 @@ internal abstract class AbstractContinuation( return } } - is CancellationHandler -> error("It's prohibited to register multiple handlers, tried to register $handler, already has $state") + is CancelHandler -> error("It's prohibited to register multiple handlers, tried to register $handler, already has $state") is CancelledContinuation -> { /* * Continuation is complete, invoke directly. @@ -188,18 +188,12 @@ internal abstract class AbstractContinuation( } } - private fun makeHandler(handler: CompletionHandler): CancellationHandlerImpl<*> { - if (handler is CancellationHandlerImpl<*>) { - require(handler.continuation === this) { "Handler has non-matching continuation ${handler.continuation}, current: $this" } - return handler - } - - return InvokeOnCancel(this, handler) - } + private fun makeHandler(handler: CompletionHandler): CancelHandler = + if (handler is CancelHandler) handler else InvokeOnCancel(handler) private fun tryCancel(state: NotCompleted, cause: Throwable?): Boolean { if (useCancellingState) { - require(state !is CancellationHandler) { "Invariant: 'Cancelling' state and cancellation handlers cannot be used together" } + require(state !is CancelHandler) { "Invariant: 'Cancelling' state and cancellation handlers cannot be used together" } return _state.compareAndSet(state, Cancelling(CancelledContinuation(this, cause))) } @@ -342,7 +336,7 @@ internal abstract class AbstractContinuation( onCompletionInternal(mode) // Invoke cancellation handlers only if necessary - if (update is CancelledContinuation && expect is CancellationHandler) { + if (update is CancelledContinuation && expect is CancelHandler) { try { expect.invoke(exceptionally?.cause) } catch (ex: Throwable) { @@ -382,18 +376,14 @@ private val ACTIVE: Active = Active() // In progress of cancellation internal class Cancelling(@JvmField val cancel: CancelledContinuation) : NotCompleted -internal abstract class CancellationHandlerImpl>(@JvmField val continuation: C) : - CancellationHandler(), NotCompleted +internal abstract class CancelHandler : CancelHandlerBase(), NotCompleted -// Wrapper for lambdas, for the performance sake CancellationHandler can be subclassed directly +// Wrapper for lambdas, for the performance sake CancelHandler can be subclassed directly private class InvokeOnCancel( // Clashes with InvokeOnCancellation - continuation: AbstractContinuation<*>, private val handler: CompletionHandler -) : CancellationHandlerImpl>(continuation) { - +) : CancelHandler() { override fun invoke(cause: Throwable?) { handler.invoke(cause) } - override fun toString() = "InvokeOnCancel[${handler.classSimpleName}@$hexAddress]" } diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt index 6e4356dd20..8fc4a5eccf 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt @@ -219,16 +219,8 @@ public fun CancellableContinuation<*>.removeOnCancel(node: LockFreeLinkedListNod * Removes a given node on cancellation. * @suppress **This is unstable API and it is subject to change.** */ -public fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode): Unit { - val handler: CompletionHandler - if (this is CancellableContinuationImpl<*>) { - handler = RemoveOnCancel(this, node).asHandler - } else { - handler = { node.remove() } - } - - invokeOnCancellation(handler) -} +public fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode) = + invokeOnCancellation(handler = RemoveOnCancel(node).asHandler) /** * Disposes a specified [handle] when this continuation is cancelled. @@ -255,41 +247,19 @@ public fun CancellableContinuation<*>.disposeOnCompletion(handle: DisposableHand * invokeOnCancellation { handle.dispose() } * ``` */ -public fun CancellableContinuation<*>.disposeOnCancellation(handle: DisposableHandle) { - val handler: CompletionHandler - if (this is CancellableContinuationImpl<*>) { - handler = DisposeOnCancellation(this, handle).asHandler - } else { - handler = { handle.dispose() } - } - - - invokeOnCancellation(handler) -} +public fun CancellableContinuation<*>.disposeOnCancellation(handle: DisposableHandle) = + invokeOnCancellation(handler = DisposeOnCancel(handle).asHandler) // --------------- implementation details --------------- -// TODO: With separate class IDEA fails -private class RemoveOnCancel( - cont: CancellableContinuationImpl<*>, - @JvmField val node: LockFreeLinkedListNode -) : CancellationHandlerImpl>(cont) { - - override fun invoke(cause: Throwable?) { - node.remove() - } - +private class RemoveOnCancel(private val node: LockFreeLinkedListNode) : CancelHandler() { + override fun invoke(cause: Throwable?) { node.remove() } override fun toString() = "RemoveOnCancel[$node]" } -private class DisposeOnCancellation( - continuation: CancellableContinuationImpl<*>, - private val handle: DisposableHandle -) : CancellationHandlerImpl>(continuation) { - +private class DisposeOnCancel(private val handle: DisposableHandle) : CancelHandler() { override fun invoke(cause: Throwable?) = handle.dispose() - - override fun toString(): String = "DisposeOnCancellation[$handle]" + override fun toString(): String = "DisposeOnCancel[$handle]" } @PublishedApi diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt index cfb63bef4f..c422d30009 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.common.kt @@ -39,17 +39,19 @@ public typealias CompletionHandler = (cause: Throwable?) -> Unit // We want class that extends LockFreeLinkedListNode & CompletionHandler but we cannot do it on Kotlin/JS, // so this expect class provides us with the corresponding abstraction in a platform-agnostic way. -internal expect abstract class CompletionHandlerNode() : LockFreeLinkedListNode { - val asHandler: CompletionHandler +internal expect abstract class CompletionHandlerBase() : LockFreeLinkedListNode { abstract fun invoke(cause: Throwable?) } -// More compact version of CompletionHandlerNode for CancellableContinuation with same workaround for JS -internal expect abstract class CancellationHandler() { - val asHandler: CompletionHandler +internal expect val CompletionHandlerBase.asHandler: CompletionHandler + +// More compact version of CompletionHandlerBase for CancellableContinuation with same workaround for JS +internal expect abstract class CancelHandlerBase() { abstract fun invoke(cause: Throwable?) } +internal expect val CancelHandlerBase.asHandler: CompletionHandler + // :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, // because we play type tricks on Kotlin/JS and handler is not necessarily a function there internal expect fun CompletionHandler.invokeIt(cause: Throwable?) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt index 2ac8865d00..1f975e550c 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/JobSupport.kt @@ -853,7 +853,7 @@ internal interface Incomplete { internal abstract class JobNode( @JvmField val job: J -) : CompletionHandlerNode(), DisposableHandle, Incomplete { +) : CompletionHandlerBase(), DisposableHandle, Incomplete { override val isActive: Boolean get() = true override val list: NodeList? get() = null override fun dispose() = (job as JobSupport).removeNode(this) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt index f0a8d5253b..8bd5b4f9f4 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt @@ -18,15 +18,17 @@ package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.internal.* -internal actual abstract class CompletionHandlerNode actual constructor() : LockFreeLinkedListNode(), CompletionHandler { - actual inline val asHandler: CompletionHandler get() = this +internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler { actual abstract override fun invoke(cause: Throwable?) } -internal actual abstract class CancellationHandler actual constructor() : CompletionHandler { - actual inline val asHandler: CompletionHandler get() = this +internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler get() = this + +internal actual abstract class CancelHandlerBase actual constructor() : CompletionHandler { actual abstract override fun invoke(cause: Throwable?) } +internal actual inline val CancelHandlerBase.asHandler: CompletionHandler get() = this + @Suppress("NOTHING_TO_INLINE") internal actual inline fun CompletionHandler.invokeIt(cause: Throwable?) = invoke(cause) \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt index a8fa9beb4d..a413854c5f 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Future.kt @@ -54,20 +54,14 @@ public fun CancellableContinuation<*>.cancelFutureOnCompletion(future: Future<*> * invokeOnCancellation { future.cancel(false) } * ``` */ -public fun CancellableContinuation<*>.cancelFutureOnCancellation(future: Future<*>) { - if (this is AbstractContinuation<*>) { - invokeOnCancellation(handler = CancelFutureOnCancellation(this, future)) - } else { - // Fallback if someone else implement CancellableContinuation - invokeOnCancellation { future.cancel(false) } - } -} +public fun CancellableContinuation<*>.cancelFutureOnCancellation(future: Future<*>) = + invokeOnCancellation(handler = CancelFutureOnCancel(future)) private class CancelFutureOnCompletion( job: Job, private val future: Future<*> ) : JobNode(job) { - override fun invoke(reason: Throwable?) { + override fun invoke(cause: Throwable?) { // Don't interrupt when cancelling future on completion, because no one is going to reset this // interruption flag and it will cause spurious failures elsewhere future.cancel(false) @@ -75,15 +69,11 @@ private class CancelFutureOnCompletion( override fun toString() = "CancelFutureOnCompletion[$future]" } -private class CancelFutureOnCancellation( - continuation: AbstractContinuation<*>, - private val future: Future<*> -) : CancellationHandlerImpl>(continuation) { - - override fun invoke(reason: Throwable?) { +private class CancelFutureOnCancel(private val future: Future<*>) : CancelHandler() { + override fun invoke(cause: Throwable?) { // Don't interrupt when cancelling future on completion, because no one is going to reset this // interruption flag and it will cause spurious failures elsewhere future.cancel(false) } - override fun toString() = "CancelFutureOnCancellation[$future]" + override fun toString() = "CancelFutureOnCancel[$future]" } diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt index ee3ce38b05..82263993e1 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CompletionHandler.kt @@ -18,21 +18,25 @@ package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.internal.* -internal actual abstract class CompletionHandlerNode : LinkedListNode() { - @Suppress("UnsafeCastFromDynamic") - actual inline val asHandler: CompletionHandler get() = asDynamic() +internal actual abstract class CompletionHandlerBase : LinkedListNode() { + @JsName("invoke") actual abstract fun invoke(cause: Throwable?) } -internal actual abstract class CancellationHandler { - @Suppress("UnsafeCastFromDynamic") - actual inline val asHandler: CompletionHandler get() = asDynamic() +@Suppress("UnsafeCastFromDynamic") +internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler get() = asDynamic() + +internal actual abstract class CancelHandlerBase { + @JsName("invoke") actual abstract fun invoke(cause: Throwable?) } +@Suppress("UnsafeCastFromDynamic") +internal actual inline val CancelHandlerBase.asHandler: CompletionHandler get() = asDynamic() + internal actual fun CompletionHandler.invokeIt(cause: Throwable?) { when(jsTypeOf(this)) { "function" -> invoke(cause) - else -> (this as CompletionHandlerNode).invoke(cause) + else -> asDynamic().invoke(cause) } } From 3e9f244e4b958801aefcfa05b9308b8220b09e53 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Sat, 28 Apr 2018 17:38:22 +0300 Subject: [PATCH 57/61] Review and optimize usage of CancellableContinuation.invokeOnCancellation --- .../kotlinx/coroutines/experimental/Await.kt | 9 +++++++-- .../experimental/channels/AbstractChannel.kt | 8 ++++++-- .../kotlinx/coroutines/experimental/nio/Nio.kt | 12 ++++++------ .../coroutines/experimental/JSDispatcher.kt | 14 ++++++++------ .../coroutines/experimental/reactive/Await.kt | 4 ++-- .../kotlinx/coroutines/experimental/rx1/RxAwait.kt | 9 ++++----- .../coroutines/experimental/rx1/RxScheduler.kt | 2 +- .../kotlinx/coroutines/experimental/rx2/RxAwait.kt | 8 ++++---- .../coroutines/experimental/rx2/RxScheduler.kt | 2 +- 9 files changed, 39 insertions(+), 29 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt index 218c3ee57c..26a59ce3b5 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -74,12 +74,17 @@ private class AwaitAll(private val deferreds: Array>) { it.start() // To properly await lazily started deferreds it.invokeOnCompletion(AwaitAllNode(cont, it).asHandler) } - cont.invokeOnCancellation { + cont.invokeOnCancellation(handler = DisposeHandlersOnCancel(handlers).asHandler) + } + + private class DisposeHandlersOnCancel(private val handlers: List) : CancelHandler() { + override fun invoke(cause: Throwable?) { handlers.forEach { it.dispose() } } + override fun toString(): String = "DisposeHandlersOnCancel[$handlers]" } - inner class AwaitAllNode(private val continuation: CancellableContinuation>, job: Job) : JobNode(job) { + private inner class AwaitAllNode(private val continuation: CancellableContinuation>, job: Job) : JobNode(job) { override fun invoke(cause: Throwable?) { if (cause != null) { val token = continuation.tryResumeWithException(cause) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt index a5889c4362..bb8ce693b7 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt @@ -769,11 +769,15 @@ public abstract class AbstractChannel : AbstractSendChannel(), Channel // ------ private ------ - private fun removeReceiveOnCancel(cont: CancellableContinuation<*>, receive: Receive<*>) { - cont.invokeOnCancellation { + private fun removeReceiveOnCancel(cont: CancellableContinuation<*>, receive: Receive<*>) = + cont.invokeOnCancellation(handler = RemoveReceiveOnCancel(receive).asHandler) + + private inner class RemoveReceiveOnCancel(private val receive: Receive<*>) : CancelHandler() { + override fun invoke(cause: Throwable?) { if (receive.remove()) onReceiveDequeued() } + override fun toString(): String = "RemoveReceiveOnCancel[$receive]" } private class Itr(val channel: AbstractChannel) : ChannelIterator { diff --git a/integration/kotlinx-coroutines-nio/src/main/kotlin/kotlinx/coroutines/experimental/nio/Nio.kt b/integration/kotlinx-coroutines-nio/src/main/kotlin/kotlinx/coroutines/experimental/nio/Nio.kt index fdcdee36b0..1d002ac17d 100644 --- a/integration/kotlinx-coroutines-nio/src/main/kotlin/kotlinx/coroutines/experimental/nio/Nio.kt +++ b/integration/kotlinx-coroutines-nio/src/main/kotlin/kotlinx/coroutines/experimental/nio/Nio.kt @@ -137,12 +137,12 @@ suspend fun AsynchronousSocketChannel.aWrite( private fun Channel.closeOnCancel(cont: CancellableContinuation<*>) { cont.invokeOnCancellation { - try { - close() - } catch (ex: Throwable) { - // Specification says that it is Ok to call it any time, but reality is different, - // so we have just to ignore exception - } + try { + close() + } catch (ex: Throwable) { + // Specification says that it is Ok to call it any time, but reality is different, + // so we have just to ignore exception + } } } diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt index 926ddaaeba..f9f1f628f1 100644 --- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt +++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/JSDispatcher.kt @@ -28,16 +28,18 @@ internal class NodeDispatcher : CoroutineDispatcher(), Delay { override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation) { val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) // Actually on cancellation, but clearTimeout is idempotent - continuation.invokeOnCancellation { clearTimeout(handle) } + continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler) + } + + private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle { + override fun dispose() { clearTimeout(handle) } + override fun invoke(cause: Throwable?) { dispose() } + override fun toString(): String = "ClearTimeout[$handle]" } override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { val handle = setTimeout({ block.run() }, time.toIntMillis(unit)) - return object : DisposableHandle { - override fun dispose() { - clearTimeout(handle) - } - } + return ClearTimeout(handle) } } diff --git a/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Await.kt b/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Await.kt index f283ba0038..0feef612fc 100644 --- a/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Await.kt +++ b/reactive/kotlinx-coroutines-reactive/src/main/kotlin/kotlinx/coroutines/experimental/reactive/Await.kt @@ -120,15 +120,15 @@ private suspend fun Publisher.awaitOne( Mode.FIRST, Mode.FIRST_OR_DEFAULT -> { if (!seenValue) { seenValue = true - cont.resume(t) subscription.cancel() + cont.resume(t) } } Mode.LAST, Mode.SINGLE -> { if (mode == Mode.SINGLE && seenValue) { + subscription.cancel() if (cont.isActive) cont.resumeWithException(IllegalArgumentException("More that one onNext value for $mode")) - subscription.cancel() } else { value = t seenValue = true diff --git a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxAwait.kt b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxAwait.kt index 0a567a7350..9838fdae45 100644 --- a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxAwait.kt +++ b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxAwait.kt @@ -33,7 +33,7 @@ import rx.* */ public suspend fun Completable.awaitCompleted(): Unit = suspendCancellableCoroutine { cont -> subscribe(object : CompletableSubscriber { - override fun onSubscribe(s: Subscription) { cont.unsubscribeOnCompletion(s) } + override fun onSubscribe(s: Subscription) { cont.unsubscribeOnCancellation(s) } override fun onCompleted() { cont.resume(Unit) } override fun onError(e: Throwable) { cont.resumeWithException(e) } }) @@ -50,7 +50,7 @@ public suspend fun Completable.awaitCompleted(): Unit = suspendCancellableCorout * immediately resumes with [CancellationException]. */ public suspend fun Single.await(): T = suspendCancellableCoroutine { cont -> - cont.unsubscribeOnCompletion(subscribe(object : SingleSubscriber() { + cont.unsubscribeOnCancellation(subscribe(object : SingleSubscriber() { override fun onSuccess(t: T) { cont.resume(t) } override fun onError(error: Throwable) { cont.resumeWithException(error) } })) @@ -128,7 +128,7 @@ public suspend fun Observable.awaitSingle(): T = single().awaitOne() // ------------------------ private ------------------------ private suspend fun Observable.awaitOne(): T = suspendCancellableCoroutine { cont -> - cont.unsubscribeOnCompletion(subscribe(object : Subscriber() { + cont.unsubscribeOnCancellation(subscribe(object : Subscriber() { override fun onStart() { request(1) } override fun onNext(t: T) { cont.resume(t) } override fun onCompleted() { if (cont.isActive) cont.resumeWithException(IllegalStateException("Should have invoked onNext")) } @@ -136,6 +136,5 @@ private suspend fun Observable.awaitOne(): T = suspendCancellableCoroutin })) } -internal fun CancellableContinuation.unsubscribeOnCompletion(sub: Subscription) { +internal fun CancellableContinuation.unsubscribeOnCancellation(sub: Subscription) = invokeOnCancellation { sub.unsubscribe() } -} diff --git a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxScheduler.kt b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxScheduler.kt index 5cf693648e..1a4d2cdfe9 100644 --- a/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxScheduler.kt +++ b/reactive/kotlinx-coroutines-rx1/src/main/kotlin/kotlinx/coroutines/experimental/rx1/RxScheduler.kt @@ -46,7 +46,7 @@ public class SchedulerCoroutineDispatcher(private val scheduler: Scheduler) : Co with(continuation) { resumeUndispatched(Unit) } }, time, unit) .let { subscription -> - continuation.unsubscribeOnCompletion(subscription) + continuation.unsubscribeOnCancellation(subscription) } override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle = diff --git a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxAwait.kt b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxAwait.kt index d0a5167563..29e265a5d9 100644 --- a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxAwait.kt +++ b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxAwait.kt @@ -34,7 +34,7 @@ import kotlinx.coroutines.experimental.suspendCancellableCoroutine */ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine { cont -> subscribe(object : CompletableObserver { - override fun onSubscribe(d: Disposable) { cont.disposeOnCompletion(d) } + override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) } override fun onComplete() { cont.resume(Unit) } override fun onError(e: Throwable) { cont.resumeWithException(e) } }) @@ -65,7 +65,7 @@ public suspend fun MaybeSource.await(): T? = (this as MaybeSource).aw */ public suspend fun MaybeSource.awaitOrDefault(default: T): T = suspendCancellableCoroutine { cont -> subscribe(object : MaybeObserver { - override fun onSubscribe(d: Disposable) { cont.disposeOnCompletion(d) } + override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) } override fun onComplete() { cont.resume(default) } override fun onSuccess(t: T) { cont.resume(t) } override fun onError(error: Throwable) { cont.resumeWithException(error) } @@ -84,7 +84,7 @@ public suspend fun MaybeSource.awaitOrDefault(default: T): T = suspendCan */ public suspend fun SingleSource.await(): T = suspendCancellableCoroutine { cont -> subscribe(object : SingleObserver { - override fun onSubscribe(d: Disposable) { cont.disposeOnCompletion(d) } + override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) } override fun onSuccess(t: T) { cont.resume(t) } override fun onError(error: Throwable) { cont.resumeWithException(error) } }) @@ -161,7 +161,7 @@ public suspend fun ObservableSource.awaitSingle(): T = awaitOne(Mode.SING // ------------------------ private ------------------------ -internal fun CancellableContinuation<*>.disposeOnCompletion(d: Disposable) = +internal fun CancellableContinuation<*>.disposeOnCancellation(d: Disposable) = invokeOnCancellation { d.dispose() } private enum class Mode(val s: String) { diff --git a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxScheduler.kt b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxScheduler.kt index 5d5e754ab7..46e0c11a10 100644 --- a/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxScheduler.kt +++ b/reactive/kotlinx-coroutines-rx2/src/main/kotlin/kotlinx/coroutines/experimental/rx2/RxScheduler.kt @@ -43,7 +43,7 @@ public class SchedulerCoroutineDispatcher(private val scheduler: Scheduler) : Co val disposable = scheduler.scheduleDirect({ with(continuation) { resumeUndispatched(Unit) } }, time, unit) - continuation.disposeOnCompletion(disposable) + continuation.disposeOnCancellation(disposable) } override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { From e0b6db04b3bd86e942bc7e40081a82aedd8fd200 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Sat, 28 Apr 2018 19:22:29 +0300 Subject: [PATCH 58/61] Leaking handles in awaitAll are fixed --- .../kotlinx/coroutines/experimental/Await.kt | 44 +++++++++++++++---- .../coroutines/experimental/AwaitJvmTest.kt | 41 +++++++++++++++++ 2 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitJvmTest.kt diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt index 26a59ce3b5..ca0b2ff6be 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Await.kt @@ -17,6 +17,7 @@ package kotlinx.coroutines.experimental import kotlinx.atomicfu.atomic +import kotlinx.coroutines.experimental.internalAnnotations.Volatile /** * Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values @@ -70,29 +71,56 @@ private class AwaitAll(private val deferreds: Array>) { private val notCompletedCount = atomic(deferreds.size) suspend fun await(): List = suspendCancellableCoroutine { cont -> - val handlers = deferreds.map { - it.start() // To properly await lazily started deferreds - it.invokeOnCompletion(AwaitAllNode(cont, it).asHandler) + // Intricate dance here + // Step 1: Create nodes and install them as completion handlers, they may fire! + val nodes = Array(deferreds.size) { i -> + val deferred = deferreds[i] + deferred.start() // To properly await lazily started deferreds + AwaitAllNode(cont, deferred).apply { + handle = deferred.invokeOnCompletion(asHandler) + } + } + val disposer = DisposeHandlersOnCancel(nodes) + // Step 2: Set disposer to each node + nodes.forEach { it.disposer = disposer } + // Here we know that if any code the nodes complete, it will dipsose the rest + // Step 3: Now we can check if continuation is complete + if (cont.isCompleted) { + // it is already complete while handlers were being installed -- dispose them all + disposer.disposeAll() + } else { + cont.invokeOnCancellation(handler = disposer.asHandler) } - cont.invokeOnCancellation(handler = DisposeHandlersOnCancel(handlers).asHandler) } - private class DisposeHandlersOnCancel(private val handlers: List) : CancelHandler() { - override fun invoke(cause: Throwable?) { - handlers.forEach { it.dispose() } + private inner class DisposeHandlersOnCancel(private val nodes: Array) : CancelHandler() { + fun disposeAll() { + nodes.forEach { it.handle.dispose() } } - override fun toString(): String = "DisposeHandlersOnCancel[$handlers]" + + override fun invoke(cause: Throwable?) { disposeAll() } + override fun toString(): String = "DisposeHandlersOnCancel[$nodes]" } private inner class AwaitAllNode(private val continuation: CancellableContinuation>, job: Job) : JobNode(job) { + lateinit var handle: DisposableHandle + + @Volatile + var disposer: DisposeHandlersOnCancel? = null + override fun invoke(cause: Throwable?) { if (cause != null) { val token = continuation.tryResumeWithException(cause) if (token != null) { continuation.completeResume(token) + // volatile read of disposer AFTER continuation is complete + val disposer = this.disposer + // and if disposer was already set (all handlers where already installed, then dispose them all) + if (disposer != null) disposer.disposeAll() } } else if (notCompletedCount.decrementAndGet() == 0) { continuation.resume(deferreds.map { it.getCompleted() }) + // Note, that all deferreds are complete here, so we don't need to dispose their nodes } } } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitJvmTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitJvmTest.kt new file mode 100644 index 0000000000..96a381f027 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AwaitJvmTest.kt @@ -0,0 +1,41 @@ +/* + * 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.junit.* + +class AwaitJvmTest : TestBase() { + @Test + public fun testSecondLeak() = runTest { + // This test is to make sure that handlers installed on the second deferred do not leak + val d1 = CompletableDeferred() + val d2 = CompletableDeferred() + d1.completeExceptionally(TestException()) // first is crashed + val iterations = 3_000_000 * stressTestMultiplier + for (iter in 1..iterations) { + try { + awaitAll(d1, d2) + expectUnreached() + } catch (e: TestException) { + expect(iter) + } + } + finish(iterations + 1) + } + + private class TestException : Exception() +} \ No newline at end of file From 87f2faa45a012ded34bb0e7c473223848fa58a25 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 30 Apr 2018 22:53:02 +0300 Subject: [PATCH 59/61] Fix compiler warnings Fixes #348 --- .../coroutines/experimental/CoroutineContext.common.kt | 1 - .../kotlin/kotlinx/coroutines/experimental/TestBase.common.kt | 2 -- .../kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt | 2 +- .../kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt | 2 +- .../main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt | 4 ++-- .../kotlin/kotlinx/coroutines/experimental/AsyncJvmTest.kt | 4 +++- .../kotlin/kotlinx/coroutines/experimental/TestBaseTest.kt | 2 +- .../kotlinx/coroutines/experimental/channels/ActorTest.kt | 2 +- .../channels/BroadcastChannelMultiReceiveStressTest.kt | 2 +- .../experimental/internal/LockFreeListLinearizabilityTest.kt | 2 +- .../coroutines/experimental/io/ByteBufferChannelTest.kt | 2 +- .../kotlin/kotlinx/coroutines/experimental/future/Future.kt | 2 +- 12 files changed, 13 insertions(+), 14 deletions(-) diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.common.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.common.kt index a60cec0902..aaaa306224 100644 --- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.common.kt +++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.common.kt @@ -18,7 +18,6 @@ package kotlinx.coroutines.experimental import kotlin.coroutines.experimental.* -@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") public expect fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext @Suppress("PropertyName") diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.common.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.common.kt index 2d76b046a5..c2b1dddd44 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.common.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.common.kt @@ -20,13 +20,11 @@ public expect open class TestBase constructor() { public val isStressTest: Boolean public val stressTestMultiplier: Int - @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") public fun error(message: Any, cause: Throwable? = null): Nothing public fun expect(index: Int) public fun expectUnreached() public fun finish(index: Int) - @Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER") public fun runTest( expected: ((Throwable) -> Boolean)? = null, unhandled: List<(Throwable) -> Boolean> = emptyList(), diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt index 5116d129a2..80b75a0c2d 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt @@ -177,7 +177,7 @@ class WithTimeoutOrNullTest : TestBase() { expected = { it is TestException } ) { expect(1) - val result = withTimeoutOrNull(100) { + withTimeoutOrNull(100) { expect(2) try { delay(1000) diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt index 220a12ba5c..7aeede24ec 100644 --- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt +++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt @@ -147,7 +147,7 @@ class WithTimeoutTest : TestBase() { expected = { it is CancellationException } ) { expect(1) - val result = withTimeout(100) { + withTimeout(100) { expect(2) try { delay(1000) diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt index 10081ef209..ab40f3dac0 100644 --- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CommonPool.kt @@ -18,8 +18,8 @@ package kotlinx.coroutines.experimental import kotlinx.coroutines.experimental.timeunit.TimeUnit import java.util.concurrent.* -import java.util.concurrent.atomic.AtomicInteger -import kotlin.coroutines.experimental.CoroutineContext +import java.util.concurrent.atomic.* +import kotlin.coroutines.experimental.* /** * Represents common pool of shared threads as coroutine dispatcher for compute-intensive tasks. diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncJvmTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncJvmTest.kt index a2f4d5cbcd..72ec5197a7 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncJvmTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/AsyncJvmTest.kt @@ -24,7 +24,9 @@ class AsyncJvmTest : TestBase() { @Test fun testAsyncWithFinally() = runTest { expect(1) - val d = async(coroutineContext) { + + @Suppress("UNREACHABLE_CODE") + val d = async(coroutineContext) { expect(3) try { yield() // to main, will cancel diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBaseTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBaseTest.kt index 2fb71a065c..d344e70fe6 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBaseTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/TestBaseTest.kt @@ -22,7 +22,7 @@ class TestBaseTest : TestBase() { @Test fun testThreadsShutdown() { val SHUTDOWN_TIMEOUT = 1_000L - repeat(1000 * stressTestMultiplier) { i -> + repeat(1000 * stressTestMultiplier) { _ -> CommonPool.usePrivatePool() val threadsBefore = currentThreads() runBlocking { diff --git a/core/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 index 268ae31409..5109ba6474 100644 --- a/core/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 @@ -106,7 +106,7 @@ class ActorTest(private val capacity: Int) : TestBase() { expect(2) require(element!! == 42) try { - val next = channel.receiveOrNull() + channel.receiveOrNull() } catch (e: IOException) { expect(3) } diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt index 60bdeb1a5f..b0f139a374 100644 --- a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannelMultiReceiveStressTest.kt @@ -91,7 +91,7 @@ class BroadcastChannelMultiReceiveStressTest( printProgress() } // wait - repeat(nSeconds) { sec -> + repeat(nSeconds) { _ -> delay(1000) printProgress() } diff --git a/core/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 index 49ed5f5f50..dca99d5ee9 100644 --- a/core/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 @@ -53,7 +53,7 @@ class LockFreeListLinearizabilityTest : TestBase() { @Operation fun removeFirstOrPeekIfNotSame(@Param(name = "value") value: Int): Int? { val node = q.removeFirstIfIsInstanceOfOrPeekIf { !it.isSame(value) } ?: return null - return (node as Node).value + return node.value } fun Any.isSame(value: Int) = this is Node && this.value == value 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 index 1ff9673ffc..7daccd9034 100644 --- 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 @@ -819,7 +819,7 @@ class ByteBufferChannelTest : TestBase() { return launch(context = DefaultDispatcher + CoroutineName(name), parent = coroutineContext[Job]) { block() }.apply { - invokeOnCompletion(true) { t -> + invokeOnCompletion( onCancelling = true) { t -> if (t != null) ch.cancel(t) } } 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 89846bd9a8..d476990ef8 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 @@ -166,7 +166,7 @@ public suspend fun CompletionStage.await(): T { // fast path when CompletableFuture is already done (does not suspend) if (this is Future<*> && isDone()) { try { - @Suppress("UNCHECKED") + @Suppress("UNCHECKED_CAST") return get() as T } catch (e: ExecutionException) { throw e.cause ?: e // unwrap original cause from ExecutionException From 20dbd9f6ee33eb3f037eb9312fa0b5a6954d60ee Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 7 May 2018 20:08:20 +0300 Subject: [PATCH 60/61] Fast-path for isDone in ListenableFuture#await, stop using deprecated API --- .../experimental/guava/ListenableFuture.kt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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 cec625b2b1..b67d8605cf 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 @@ -18,6 +18,7 @@ package kotlinx.coroutines.experimental.guava import com.google.common.util.concurrent.* import kotlinx.coroutines.experimental.* +import java.util.concurrent.* import kotlin.coroutines.experimental.* /** @@ -126,11 +127,19 @@ private class DeferredListenableFuture( * care is taken to clear the reference to the waiting coroutine itself, so that its memory can be released even if * the future never completes. */ -public suspend fun ListenableFuture.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation -> - val callback = ContinuationCallback(cont) - Futures.addCallback(this, callback) - cont.invokeOnCancellation { - callback.cont = null // clear the reference to continuation from the future's callback +public suspend fun ListenableFuture.await(): T { + try { + if (isDone) return get() as T + } catch (e: ExecutionException) { + throw e.cause ?: e // unwrap original cause from ExecutionException + } + + return suspendCancellableCoroutine { cont: CancellableContinuation -> + val callback = ContinuationCallback(cont) + Futures.addCallback(this, callback, MoreExecutors.directExecutor()) + cont.invokeOnCancellation { + callback.cont = null // clear the reference to continuation from the future's callback + } } } From afb3dea7e19a8733ed3a7540829471d16f78dd1c Mon Sep 17 00:00:00 2001 From: Anton Spaans Date: Tue, 20 Mar 2018 18:28:53 -0400 Subject: [PATCH 61/61] Adding a test-helper class TestCoroutineContext. --- core/kotlinx-coroutines-core/README.md | 10 + .../experimental/internal/ThreadSafeHeap.kt | 7 + .../experimental/test/TestCoroutineContext.kt | 285 ++++++++++++ .../test/TestCoroutineContextTest.kt | 419 ++++++++++++++++++ 4 files changed, 721 insertions(+) create mode 100644 core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/test/TestCoroutineContext.kt create mode 100644 core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/test/TestCoroutineContextTest.kt diff --git a/core/kotlinx-coroutines-core/README.md b/core/kotlinx-coroutines-core/README.md index c32be5029d..e4bcce0c43 100644 --- a/core/kotlinx-coroutines-core/README.md +++ b/core/kotlinx-coroutines-core/README.md @@ -69,6 +69,10 @@ This module provides debugging facilities for coroutines (run JVM with `-ea` or and [newCoroutineContext] function to write user-defined coroutine builders that work with these debugging facilities. +This module provides a special CoroutineContext type [TestCoroutineCoroutineContext][kotlinx.coroutines.experimental.test.TestCoroutineContext] that +allows the writer of code that contains Coroutines with delays and timeouts to write non-flaky unit-tests for that code allowing these tests to +terminate in near zero time. See the documentation for this class for more information. + # Package kotlinx.coroutines.experimental General-purpose coroutine builders, contexts, and helper functions. @@ -93,6 +97,10 @@ Low-level primitives for finer-grained control of coroutines. Optional time unit support for multiplatform projects. +# Package kotlinx.coroutines.experimental.test + +Components to ease writing unit-tests for code that contains coroutines with delays and timeouts. + [launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/launch.html @@ -148,4 +156,6 @@ Optional time unit support for multiplatform projects. [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.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/-select-builder/on-timeout.html + +[kotlinx.coroutines.experimental.test.TestCoroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.test/-test-coroutine-context/index.html diff --git a/core/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 index 3b239f78c0..5f12f26639 100644 --- a/core/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 @@ -16,6 +16,8 @@ package kotlinx.coroutines.experimental.internal +import java.util.* + /** * @suppress **This is unstable API and it is subject to change.** */ @@ -36,6 +38,11 @@ public class ThreadSafeHeap where T: ThreadSafeHeapNode, T: Comparable { public val isEmpty: Boolean get() = size == 0 + public fun clear() = synchronized(this) { + Arrays.fill(a, 0, size, null) + size = 0 + } + public fun peek(): T? = synchronized(this) { firstImpl() } public fun removeFirstOrNull(): T? = synchronized(this) { diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/test/TestCoroutineContext.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/test/TestCoroutineContext.kt new file mode 100644 index 0000000000..190efe64ef --- /dev/null +++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/test/TestCoroutineContext.kt @@ -0,0 +1,285 @@ +/* + * Copyright 2016-2018 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.test + +import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.internal.* +import java.util.concurrent.TimeUnit +import kotlin.coroutines.experimental.* + +/** + * This [CoroutineContext] dispatcher can be used to simulate virtual time to speed up + * code, especially tests, that deal with delays and timeouts in Coroutines. + * + * Provide an instance of this TestCoroutineContext when calling the *non-blocking* [launch] or [async] + * and then advance time or trigger the actions to make the co-routines execute as soon as possible. + * + * This works much like the *TestScheduler* in RxJava2, which allows to speed up tests that deal + * with non-blocking Rx chains that contain delays, timeouts, intervals and such. + * + * This dispatcher can also handle *blocking* coroutines that are started by [runBlocking]. + * This dispatcher's virtual time will be automatically advanced based based on the delayed actions + * within the Coroutine(s). + * + * @param name A user-readable name for debugging purposes. + */ +class TestCoroutineContext(private val name: String? = null) : CoroutineContext { + private val uncaughtExceptions = mutableListOf() + + private val ctxDispatcher = Dispatcher() + + private val ctxHandler = CoroutineExceptionHandler { _, exception -> + uncaughtExceptions += exception + } + + // The ordered queue for the runnable tasks. + private val queue = ThreadSafeHeap() + + // The per-scheduler global order counter. + private var counter = 0L + + // Storing time in nanoseconds internally. + private var time = 0L + + /** + * Exceptions that were caught during a [launch] or a [async] + [Deferred.await]. + */ + public val exceptions: List get() = uncaughtExceptions + + // -- CoroutineContext implementation + + public override fun fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R = + operation(operation(initial, ctxDispatcher), ctxHandler) + + @Suppress("UNCHECKED_CAST") + public override fun get(key: CoroutineContext.Key): E? = when { + key === ContinuationInterceptor -> ctxDispatcher as E + key === CoroutineExceptionHandler -> ctxHandler as E + else -> null + } + + public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext = when { + key === ContinuationInterceptor -> ctxHandler + key === CoroutineExceptionHandler -> ctxDispatcher + else -> this + } + + /** + * Returns the current virtual clock-time as it is known to this CoroutineContext. + * + * @param unit The [TimeUnit] in which the clock-time must be returned. + * @return The virtual clock-time + */ + public fun now(unit: TimeUnit = TimeUnit.MILLISECONDS)= + unit.convert(time, TimeUnit.NANOSECONDS) + + /** + * Moves the CoroutineContext's virtual clock forward by a specified amount of time. + * + * The returned delay-time can be larger than the specified delay-time if the code + * under test contains *blocking* Coroutines. + * + * @param delayTime The amount of time to move the CoroutineContext's clock forward. + * @param unit The [TimeUnit] in which [delayTime] and the return value is expressed. + * @return The amount of delay-time that this CoroutinesContext's clock has been forwarded. + */ + public fun advanceTimeBy(delayTime: Long, unit: TimeUnit = TimeUnit.MILLISECONDS): Long { + val oldTime = time + advanceTimeTo(oldTime + unit.toNanos(delayTime), TimeUnit.NANOSECONDS) + return unit.convert(time - oldTime, TimeUnit.NANOSECONDS) + } + + /** + * Moves the CoroutineContext's clock-time to a particular moment in time. + * + * @param targetTime The point in time to which to move the CoroutineContext's clock. + * @param unit The [TimeUnit] in which [targetTime] is expressed. + */ + fun advanceTimeTo(targetTime: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) { + val nanoTime = unit.toNanos(targetTime) + triggerActions(nanoTime) + if (nanoTime > time) time = nanoTime + } + + /** + * Triggers any actions that have not yet been triggered and that are scheduled to be triggered at or + * before this CoroutineContext's present virtual clock-time. + */ + public fun triggerActions() = triggerActions(time) + + /** + * Cancels all not yet triggered actions. Be careful calling this, since it can seriously + * mess with your coroutines work. This method should usually be called on tear-down of a + * unit test. + */ + public fun cancelAllActions() { + // An 'is-empty' test is required to avoid a NullPointerException in the 'clear()' method + if (!queue.isEmpty) queue.clear() + } + + /** + * This method does nothing if there is one unhandled exception that satisfies the given predicate. + * Otherwise it throws an [AssertionError] with the given message. + * + * (this method will clear the list of unhandled exceptions) + * + * @param message Message of the [AssertionError]. Defaults to an empty String. + * @param predicate The predicate that must be satisfied. + */ + public fun assertUnhandledException(message: String = "", predicate: (Throwable) -> Boolean) { + if (uncaughtExceptions.size != 1 || !predicate(uncaughtExceptions[0])) throw AssertionError(message) + uncaughtExceptions.clear() + } + + /** + * This method does nothing if there are no unhandled exceptions or all of them satisfy the given predicate. + * Otherwise it throws an [AssertionError] with the given message. + * + * (this method will clear the list of unhandled exceptions) + * + * @param message Message of the [AssertionError]. Defaults to an empty String. + * @param predicate The predicate that must be satisfied. + */ + public fun assertAllUnhandledExceptions(message: String = "", predicate: (Throwable) -> Boolean) { + if (!uncaughtExceptions.all(predicate)) throw AssertionError(message) + uncaughtExceptions.clear() + } + + /** + * This method does nothing if one or more unhandled exceptions satisfy the given predicate. + * Otherwise it throws an [AssertionError] with the given message. + * + * (this method will clear the list of unhandled exceptions) + * + * @param message Message of the [AssertionError]. Defaults to an empty String. + * @param predicate The predicate that must be satisfied. + */ + public fun assertAnyUnhandledException(message: String = "", predicate: (Throwable) -> Boolean) { + if (!uncaughtExceptions.any(predicate)) throw AssertionError(message) + uncaughtExceptions.clear() + } + + /** + * This method does nothing if the list of unhandled exceptions satisfy the given predicate. + * Otherwise it throws an [AssertionError] with the given message. + * + * (this method will clear the list of unhandled exceptions) + * + * @param message Message of the [AssertionError]. Defaults to an empty String. + * @param predicate The predicate that must be satisfied. + */ + public fun assertExceptions(message: String = "", predicate: (List) -> Boolean) { + if (!predicate(uncaughtExceptions)) throw AssertionError(message) + uncaughtExceptions.clear() + } + + private fun post(block: Runnable) = + queue.addLast(TimedRunnable(block, counter++)) + + private fun postDelayed(block: Runnable, delayTime: Long) = + TimedRunnable(block, counter++, time + TimeUnit.MILLISECONDS.toNanos(delayTime)) + .also { + queue.addLast(it) + } + + private fun processNextEvent(): Long { + val current = queue.peek() + if (current != null) { + /** Automatically advance time for [EventLoop]-callbacks */ + triggerActions(current.time) + } + return if (queue.isEmpty) Long.MAX_VALUE else 0L + } + + private fun triggerActions(targetTime: Long) { + while (true) { + val current = queue.removeFirstIf { it.time <= targetTime } ?: break + // If the scheduled time is 0 (immediate) use current virtual time + if (current.time != 0L) time = current.time + current.run() + } + } + + public override fun toString(): String = name ?: "TestCoroutineContext@$hexAddress" + + private inner class Dispatcher : CoroutineDispatcher(), Delay, EventLoop { + override fun dispatch(context: CoroutineContext, block: Runnable) = post(block) + + override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation) { + postDelayed(Runnable { + with(continuation) { resumeUndispatched(Unit) } + }, unit.toMillis(time)) + } + + override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { + val node = postDelayed(block, unit.toMillis(time)) + return object : DisposableHandle { + override fun dispose() { + queue.remove(node) + } + } + } + + override fun processNextEvent() = this@TestCoroutineContext.processNextEvent() + + public override fun toString(): String = "Dispatcher(${this@TestCoroutineContext})" + } +} + +private class TimedRunnable( + private val run: Runnable, + private val count: Long = 0, + @JvmField internal val time: Long = 0 +) : Comparable, Runnable by run, ThreadSafeHeapNode { + override var index: Int = 0 + + override fun run() = run.run() + + override fun compareTo(other: TimedRunnable) = if (time == other.time) { + count.compareTo(other.count) + } else { + time.compareTo(other.time) + } + + override fun toString() = "TimedRunnable(time=$time, run=$run)" +} + +/** + * Executes a block of code in which a unit-test can be written using the provided [TestCoroutineContext]. The provided + * [TestCoroutineContext] is available in the [testBody] as the `this` receiver. + * + * The [testBody] is executed and an [AssertionError] is thrown if the list of unhandled exceptions is not empty and + * contains any exception that is not a [CancellationException]. + * + * If the [testBody] successfully executes one of the [TestCoroutineContext.assertAllUnhandledExceptions], + * [TestCoroutineContext.assertAnyUnhandledException], [TestCoroutineContext.assertUnhandledException] or + * [TestCoroutineContext.assertExceptions], the list of unhandled exceptions will have been cleared and this method will + * not throw an [AssertionError]. + * + * @param testContext The provided [TestCoroutineContext]. If not specified, a default [TestCoroutineContext] will be + * provided instead. + * @param testBody The code of the unit-test. + */ +public fun withTestContext(testContext: TestCoroutineContext = TestCoroutineContext(), testBody: TestCoroutineContext.() -> Unit) { + with (testContext) { + testBody() + + if (!exceptions.all { it is CancellationException }) { + throw AssertionError("Coroutine encountered unhandled exceptions:\n${exceptions}") + } + } +} diff --git a/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/test/TestCoroutineContextTest.kt b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/test/TestCoroutineContextTest.kt new file mode 100644 index 0000000000..dcb6288f50 --- /dev/null +++ b/core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/test/TestCoroutineContextTest.kt @@ -0,0 +1,419 @@ +/* + * Copyright 2016-2018 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.test + +import kotlinx.coroutines.experimental.* +import org.junit.After +import org.junit.Assert.* +import org.junit.Test + +class TestCoroutineContextTest { + private val injectedContext = TestCoroutineContext() + + @After + fun tearDown() { + injectedContext.cancelAllActions() + } + + @Test + fun testDelayWithLaunch() = withTestContext(injectedContext) { + val delay = 1000L + + var executed = false + launch { + suspendedDelayedAction(delay) { + executed = true + } + } + + advanceTimeBy(delay / 2) + assertFalse(executed) + + advanceTimeBy(delay / 2) + assertTrue(executed) + } + + @Test + fun testTimeJumpWithLaunch() = withTestContext(injectedContext) { + val delay = 1000L + + var executed = false + launch { + suspendedDelayedAction(delay) { + executed = true + } + } + + advanceTimeTo(delay / 2) + assertFalse(executed) + + advanceTimeTo(delay) + assertTrue(executed) + } + + @Test + fun testDelayWithAsync() = withTestContext(injectedContext) { + val delay = 1000L + + var executed = false + async { + suspendedDelayedAction(delay) { + executed = true + } + } + + advanceTimeBy(delay / 2) + assertFalse(executed) + + advanceTimeBy(delay / 2) + assertTrue(executed) + } + + @Test + fun testDelayWithRunBlocking() = withTestContext(injectedContext) { + val delay = 1000L + + var executed = false + runBlocking { + suspendedDelayedAction(delay) { + executed = true + } + } + + assertTrue(executed) + assertEquals(delay, now()) + } + + private suspend fun suspendedDelayedAction(delay: Long, action: () -> Unit) { + delay(delay) + action() + } + + @Test + fun testDelayedFunctionWithRunBlocking() = withTestContext(injectedContext) { + val delay = 1000L + val expectedValue = 16 + + val result = runBlocking { + suspendedDelayedFunction(delay) { + expectedValue + } + } + + assertEquals(expectedValue, result) + assertEquals(delay, now()) + } + + @Test + fun testDelayedFunctionWithAsync() = withTestContext(injectedContext) { + val delay = 1000L + val expectedValue = 16 + + val deferred = async { + suspendedDelayedFunction(delay) { + expectedValue + } + } + + advanceTimeBy(delay / 2) + try { + deferred.getCompleted() + fail("The Job should not have been completed yet.") + } catch (e: Exception) { + // Success. + } + + advanceTimeBy(delay / 2) + assertEquals(expectedValue, deferred.getCompleted()) + } + + private suspend fun TestCoroutineContext.suspendedDelayedFunction(delay: Long, function: () -> T): T { + delay(delay / 4) + return async { + delay((delay / 4) * 3) + function() + }.await() + } + + @Test + fun testBlockingFunctionWithRunBlocking() = withTestContext(injectedContext) { + val delay = 1000L + val expectedValue = 16 + + val result = runBlocking { + suspendedBlockingFunction(delay) { + expectedValue + } + } + + assertEquals(expectedValue, result) + assertEquals(delay, now()) + } + + @Test + fun testBlockingFunctionWithAsync() = withTestContext(injectedContext) { + val delay = 1000L + val expectedValue = 16 + var now = 0L + + val deferred = async { + suspendedBlockingFunction(delay) { + expectedValue + } + } + + now += advanceTimeBy((delay / 4) - 1) + assertEquals((delay / 4) - 1, now) + assertEquals(now, now()) + try { + deferred.getCompleted() + fail("The Job should not have been completed yet.") + } catch (e: Exception) { + // Success. + } + + now += advanceTimeBy(1) + assertEquals(delay, now()) + assertEquals(now, now()) + assertEquals(expectedValue, deferred.getCompleted()) + } + + private suspend fun TestCoroutineContext.suspendedBlockingFunction(delay: Long, function: () -> T): T { + delay(delay / 4) + return runBlocking { + delay((delay / 4) * 3) + function() + } + } + + @Test + fun testTimingOutFunctionWithAsyncAndNoTimeout() = withTestContext(injectedContext) { + val delay = 1000L + val expectedValue = 67 + + val result = async { + suspendedTimingOutFunction(delay, delay + 1) { + expectedValue + } + } + + triggerActions() + assertEquals(expectedValue, result.getCompleted()) + } + + @Test + fun testTimingOutFunctionWithAsyncAndTimeout() = withTestContext(injectedContext) { + val delay = 1000L + val expectedValue = 67 + + val result = async { + suspendedTimingOutFunction(delay, delay) { + expectedValue + } + } + + triggerActions() + assertTrue(result.getCompletionExceptionOrNull() is TimeoutCancellationException) + } + + @Test + fun testTimingOutFunctionWithRunBlockingAndTimeout() = withTestContext(injectedContext) { + val delay = 1000L + val expectedValue = 67 + + try { + runBlocking { + suspendedTimingOutFunction(delay, delay) { + expectedValue + } + } + fail("Expected TimeoutCancellationException to be thrown.") + } catch (e: TimeoutCancellationException) { + // Success + } catch (e: Throwable) { + fail("Expected TimeoutCancellationException to be thrown: $e") + } + } + + private suspend fun TestCoroutineContext.suspendedTimingOutFunction(delay: Long, timeOut: Long, function: () -> T): T { + return runBlocking { + withTimeout(timeOut) { + delay(delay / 2) + val ret = function() + delay(delay / 2) + ret + } + } + } + + @Test(expected = AssertionError::class) + fun testWithTestContextThrowingAnAssertionError() = withTestContext(injectedContext) { + val expectedError = IllegalAccessError("hello") + + launch { + throw expectedError + } + + triggerActions() + } + + @Test + fun testExceptionHandlingWithLaunch() = withTestContext(injectedContext) { + val expectedError = IllegalAccessError("hello") + + launch { + throw expectedError + } + + triggerActions() + assertUnhandledException { it === expectedError} + } + + @Test + fun testExceptionHandlingWithLaunchingChildCoroutines() = withTestContext(injectedContext) { + val delay = 1000L + val expectedError = IllegalAccessError("hello") + val expectedValue = 12 + + launch { + suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, true) + } + + advanceTimeBy(delay) + assertUnhandledException { it === expectedError} + } + + @Test + fun testExceptionHandlingWithAsyncAndDontWaitForException() = withTestContext(injectedContext) { + val delay = 1000L + val expectedError = IllegalAccessError("hello") + val expectedValue = 12 + + val result = async { + suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, false) + } + + advanceTimeBy(delay) + + assertNull(result.getCompletionExceptionOrNull()) + assertEquals(expectedValue, result.getCompleted()) + } + + @Test + fun testExceptionHandlingWithAsyncAndWaitForException() = withTestContext(injectedContext) { + val delay = 1000L + val expectedError = IllegalAccessError("hello") + val expectedValue = 12 + + val result = async { + suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, true) + } + + advanceTimeBy(delay) + + val e = result.getCompletionExceptionOrNull() + assertTrue("Expected to be thrown: '$expectedError' but was '$e'", expectedError === e) + } + + @Test + fun testExceptionHandlingWithRunBlockingAndDontWaitForException() = withTestContext(injectedContext) { + val delay = 1000L + val expectedError = IllegalAccessError("hello") + val expectedValue = 12 + + val result = runBlocking { + suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, false) + } + + advanceTimeBy(delay) + + assertEquals(expectedValue, result) + } + + @Test + fun testExceptionHandlingWithRunBlockingAndWaitForException() = withTestContext(injectedContext) { + val delay = 1000L + val expectedError = IllegalAccessError("hello") + val expectedValue = 12 + + try { + runBlocking { + suspendedAsyncWithExceptionAfterDelay(delay, expectedError, expectedValue, true) + } + fail("Expected to be thrown: '$expectedError'") + } catch (e: AssertionError) { + throw e + } catch (e: Throwable) { + assertTrue("Expected to be thrown: '$expectedError' but was '$e'", expectedError === e) + } + } + + private suspend fun TestCoroutineContext.suspendedAsyncWithExceptionAfterDelay(delay: Long, exception: Throwable, value: T, await: Boolean): T { + val deferred = async { + delay(delay - 1) + throw exception + } + + if (await) { + deferred.await() + } + return value + } + + @Test + fun testCancellationException() = withTestContext { + val job = launch { + delay(1000) + } + + advanceTimeBy(500) + assertTrue(job.cancel()) + + assertAllUnhandledExceptions { it is CancellationException } + } + + @Test + fun testCancellationExceptionNotThrownByWithTestContext() = withTestContext { + val job = launch { + delay(1000) + } + + advanceTimeBy(500) + assertTrue(job.cancel()) + } +} + + +/* Some helper functions */ +private fun TestCoroutineContext.launch( + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + onCompletion: CompletionHandler? = null, + block: suspend CoroutineScope.() -> Unit +) = launch(this, start, parent, onCompletion, block) + +private fun TestCoroutineContext.async( + start: CoroutineStart = CoroutineStart.DEFAULT, + parent: Job? = null, + onCompletion: CompletionHandler? = null, + block: suspend CoroutineScope.() -> T + +) = async(this, start, parent, onCompletion, block) + +private fun TestCoroutineContext.runBlocking( + block: suspend CoroutineScope.() -> T +) = runBlocking(this, block)