Skip to content

Commit b555d91

Browse files
committed
Introduced ReceiveChannel.cancel method;
all operators on ReceiveChannel fully consume the original channel using a helper consume extension, which is reflected in docs; removed `suspend` modifier from intermediate channel operators; consistently renamed channel type param to <E>; added two versions for xxxTo fun -- with MutableList & SendChannel; added tests for all channel operators; dropped/deprecated ActorJob/ProducerJob, fixes #127
1 parent 66d18c0 commit b555d91

File tree

26 files changed

+1496
-789
lines changed

26 files changed

+1496
-789
lines changed

core/kotlinx-coroutines-core/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Coroutine builder functions:
88
| ------------- | ------------- | ---------------- | ---------------
99
| [launch] | [Job] | [CoroutineScope] | Launches coroutine that does not have any result
1010
| [async] | [Deferred] | [CoroutineScope] | Returns a single value with the future result
11-
| [produce][kotlinx.coroutines.experimental.channels.produce] | [ProducerJob][kotlinx.coroutines.experimental.channels.ProducerJob] | [ProducerScope][kotlinx.coroutines.experimental.channels.ProducerScope] | Produces a stream of elements
12-
| [actor][kotlinx.coroutines.experimental.channels.actor] | [ActorJob][kotlinx.coroutines.experimental.channels.ActorJob] | [ActorScope][kotlinx.coroutines.experimental.channels.ActorScope] | Processes a stream of messages
11+
| [produce][kotlinx.coroutines.experimental.channels.produce] | [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [ProducerScope][kotlinx.coroutines.experimental.channels.ProducerScope] | Produces a stream of elements
12+
| [actor][kotlinx.coroutines.experimental.channels.actor] | [SendChannel][kotlinx.coroutines.experimental.channels.SendChannel] | [ActorScope][kotlinx.coroutines.experimental.channels.ActorScope] | Processes a stream of messages
1313
| [runBlocking] | `T` | [CoroutineScope] | Blocks the thread while the coroutine runs
1414

1515
Coroutine dispatchers implementing [CoroutineDispatcher]:

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
898898

899899
protected open val hasCancellingState: Boolean get() = false
900900

901-
public final override fun cancel(cause: Throwable?): Boolean =
901+
public override fun cancel(cause: Throwable?): Boolean =
902902
if (hasCancellingState)
903903
makeCancelling(cause) else
904904
makeCancelled(cause)

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/AbstractChannel.kt

+28-1
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,11 @@ public abstract class AbstractSendChannel<E> : SendChannel<E> {
398398
remove()
399399
}
400400

401+
override fun resumeSendClosed(closed: Closed<*>) {
402+
if (select.trySelect(null))
403+
select.resumeSelectCancellableWithException(closed.sendException)
404+
}
405+
401406
override fun toString(): String = "SendSelect($pollResult)[$channel, $select]"
402407
}
403408

@@ -407,6 +412,7 @@ public abstract class AbstractSendChannel<E> : SendChannel<E> {
407412
override val pollResult: Any? get() = element
408413
override fun tryResumeSend(idempotent: Any?): Any? = SEND_RESUMED
409414
override fun completeResumeSend(token: Any) { check(token === SEND_RESUMED) }
415+
override fun resumeSendClosed(closed: Closed<*>) {}
410416
}
411417
}
412418

@@ -567,6 +573,24 @@ public abstract class AbstractChannel<E> : AbstractSendChannel<E>(), Channel<E>
567573
return if (result === POLL_FAILED) null else receiveOrNullResult(result)
568574
}
569575

576+
override fun cancel(cause: Throwable?): Boolean =
577+
close(cause).also {
578+
cleanupSendQueueOnCancel()
579+
}
580+
581+
// Note: this function is invoked when channel is already closed
582+
protected open fun cleanupSendQueueOnCancel() {
583+
val closed = closedForSend ?: error("Cannot happen")
584+
while (true) {
585+
val send = takeFirstSendOrPeekClosed() ?: error("Cannot happen")
586+
if (send is Closed<*>) {
587+
check(send === closed)
588+
return // cleaned
589+
}
590+
send.resumeSendClosed(closed)
591+
}
592+
}
593+
570594
public final override fun iterator(): ChannelIterator<E> = Itr(this)
571595

572596
// ------ registerSelectReceive ------
@@ -909,6 +933,7 @@ public interface Send {
909933
val pollResult: Any? // E | Closed
910934
fun tryResumeSend(idempotent: Any?): Any?
911935
fun completeResumeSend(token: Any)
936+
fun resumeSendClosed(closed: Closed<*>)
912937
}
913938

914939
/**
@@ -922,7 +947,7 @@ public interface ReceiveOrClosed<in E> {
922947
}
923948

924949
/**
925-
* Represents closed channel.
950+
* Represents sender for a specific element.
926951
* @suppress **This is unstable API and it is subject to change.**
927952
*/
928953
@Suppress("UNCHECKED_CAST")
@@ -932,6 +957,7 @@ public class SendElement(
932957
) : LockFreeLinkedListNode(), Send {
933958
override fun tryResumeSend(idempotent: Any?): Any? = cont.tryResume(Unit, idempotent)
934959
override fun completeResumeSend(token: Any) = cont.completeResume(token)
960+
override fun resumeSendClosed(closed: Closed<*>) = cont.resumeWithException(closed.sendException)
935961
override fun toString(): String = "SendElement($pollResult)[$cont]"
936962
}
937963

@@ -951,6 +977,7 @@ public class Closed<in E>(
951977
override fun completeResumeSend(token: Any) { check(token === CLOSE_RESUMED) }
952978
override fun tryResumeReceive(value: E, idempotent: Any?): Any? = CLOSE_RESUMED
953979
override fun completeResumeReceive(token: Any) { check(token === CLOSE_RESUMED) }
980+
override fun resumeSendClosed(closed: Closed<*>) = error("Should be never invoked")
954981
override fun toString(): String = "Closed[$closeCause]"
955982
}
956983

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt

+5-10
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,11 @@ public interface ActorScope<E> : CoroutineScope, ReceiveChannel<E> {
3737
}
3838

3939
/**
40-
* Return type for [actor] coroutine builder.
40+
* @suppress **Deprecated**: Use `SendChannel`.
4141
*/
42-
public interface ActorJob<in E> : Job, SendChannel<E> {
43-
/**
44-
* A reference to the mailbox channel that this coroutine is receiving messages from.
45-
* All the [SendChannel] functions on this interface delegate to
46-
* the channel instance returned by this function.
47-
*/
42+
@Deprecated(message = "Use `SendChannel`", replaceWith = ReplaceWith("SendChannel"))
43+
interface ActorJob<in E> : SendChannel<E> {
44+
@Deprecated(message = "Use SendChannel itself")
4845
val channel: SendChannel<E>
4946
}
5047

@@ -87,7 +84,7 @@ public fun <E> actor(
8784
capacity: Int = 0,
8885
start: CoroutineStart = CoroutineStart.DEFAULT,
8986
block: suspend ActorScope<E>.() -> Unit
90-
): ActorJob<E> {
87+
): SendChannel<E> {
9188
val newContext = newCoroutineContext(context)
9289
val channel = Channel<E>(capacity)
9390
val coroutine = if (start.isLazy)
@@ -109,8 +106,6 @@ private class LazyActorCoroutine<E>(
109106
channel: Channel<E>,
110107
private val block: suspend ActorScope<E>.() -> Unit
111108
) : ActorCoroutine<E>(parentContext, channel, active = false), SelectClause2<E, SendChannel<E>> {
112-
override val channel: Channel<E> get() = this
113-
114109
override fun onStart() {
115110
block.startCoroutineCancellable(this, this)
116111
}

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayBroadcastChannel.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,10 @@ class ArrayBroadcastChannel<E>(
209209
override val isBufferAlwaysFull: Boolean get() = error("Should not be used")
210210
override val isBufferFull: Boolean get() = error("Should not be used")
211211

212-
override fun close() {
213-
if (close(cause = null))
214-
broadcastChannel.updateHead(removeSub = this)
215-
}
212+
override fun cancel(cause: Throwable?): Boolean =
213+
close(cause).also { closed ->
214+
if (closed) broadcastChannel.updateHead(removeSub = this)
215+
}
216216

217217
// returns true if subHead was updated and broadcast channel's head must be checked
218218
// this method is lock-free (it never waits on lock)

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ArrayChannel.kt

+14
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,18 @@ public open class ArrayChannel<E>(
231231
send!!.completeResumeSend(token!!)
232232
return result
233233
}
234+
235+
// Note: this function is invoked when channel is already closed
236+
override fun cleanupSendQueueOnCancel() {
237+
// clear buffer first
238+
lock.withLock {
239+
repeat(size) {
240+
buffer[head] = 0
241+
head = (head + 1) % capacity
242+
}
243+
size = 0
244+
}
245+
// then clean all queued senders
246+
super.cleanupSendQueueOnCancel()
247+
}
234248
}

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/BroadcastChannel.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ public fun <E> BroadcastChannel(capacity: Int): BroadcastChannel<E> =
7676
/**
7777
* Return type for [BroadcastChannel.openSubscription] that can be used to [receive] elements from the
7878
* open subscription and to [close] it to unsubscribe.
79+
*
80+
* Note, that invocation of [cancel] also closes subscription.
7981
*/
8082
public interface SubscriptionReceiveChannel<out T> : ReceiveChannel<T>, Closeable {
8183
/**
82-
* Closes this subscription.
84+
* Closes this subscription. This is a synonym for [cancel].
8385
*/
84-
public override fun close()
86+
public override fun close() { cancel() }
8587
}

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Channel.kt

+18-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ public interface SendChannel<in E> {
8787
/**
8888
* Closes this channel with an optional exceptional [cause].
8989
* This is an idempotent operation -- repeated invocations of this function have no effect and return `false`.
90-
* Conceptually, its sends a special "close token" over this channel. Immediately after invocation of this function
90+
* Conceptually, its sends a special "close token" over this channel.
91+
*
92+
* Immediately after invocation of this function
9193
* [isClosedForSend] starts returning `true`. However, [isClosedForReceive][ReceiveChannel.isClosedForReceive]
9294
* on the side of [ReceiveChannel] starts returning `true` only after all previously sent elements
9395
* are received.
@@ -192,6 +194,21 @@ public interface ReceiveChannel<out E> {
192194
* throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
193195
*/
194196
public operator fun iterator(): ChannelIterator<E>
197+
198+
/**
199+
* Cancels reception of remaining elements from this channel. This function closes the channel with
200+
* the specified cause (unless it was already closed) and removes all buffered sent elements from it.
201+
* This function returns `true` if the channel was not closed previously, or `false` otherwise.
202+
*
203+
* Immediately after invocation of this function [isClosedForReceive] and
204+
* [isClosedForSend][SendChannel.isClosedForSend]
205+
* on the side of [SendChannel] start returning `true`, so all attempts to send to this channel
206+
* afterwards will throw [ClosedSendChannelException], while attempts to receive will throw
207+
* [ClosedReceiveChannelException] if it was cancelled without a cause.
208+
* A channel that was cancelled with non-null [cause] is called a _failed_ channel. Attempts to send or
209+
* receive on a failed channel throw the specified [cause] exception.
210+
*/
211+
public fun cancel(cause: Throwable? = null): Boolean
195212
}
196213

197214
/**

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt

+9-5
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,22 @@
1717
package kotlinx.coroutines.experimental.channels
1818

1919
import kotlinx.coroutines.experimental.AbstractCoroutine
20-
import kotlinx.coroutines.experimental.JobSupport
2120
import kotlinx.coroutines.experimental.handleCoroutineException
2221
import kotlin.coroutines.experimental.CoroutineContext
2322

2423
internal open class ChannelCoroutine<E>(
2524
parentContext: CoroutineContext,
26-
open val channel: Channel<E>,
25+
channel: Channel<E>,
2726
active: Boolean
2827
) : AbstractCoroutine<Unit>(parentContext, active), Channel<E> by channel {
29-
override fun afterCompletion(state: Any?, mode: Int) {
30-
val cause = (state as? JobSupport.CompletedExceptionally)?.cause
31-
if (!channel.close(cause) && cause != null)
28+
val channel: Channel<E>
29+
get() = this
30+
31+
override fun onCancellation(exceptionally: CompletedExceptionally?) {
32+
val cause = exceptionally?.cause
33+
if (!close(cause) && cause != null)
3234
handleCoroutineException(context, cause)
3335
}
36+
37+
override fun cancel(cause: Throwable?): Boolean = super.cancel(cause)
3438
}

0 commit comments

Comments
 (0)