|
4 | 4 |
|
5 | 5 | package kotlinx.coroutines.experimental.channels
|
6 | 6 |
|
| 7 | +import kotlinx.atomicfu.* |
7 | 8 | import kotlinx.coroutines.experimental.*
|
8 | 9 | import kotlinx.coroutines.experimental.internal.*
|
9 | 10 | import kotlinx.coroutines.experimental.internalAnnotations.*
|
@@ -32,6 +33,9 @@ public abstract class AbstractSendChannel<E> : SendChannel<E> {
|
32 | 33 | */
|
33 | 34 | protected abstract val isBufferFull: Boolean
|
34 | 35 |
|
| 36 | + // State transitions: null -> handler -> HANDLER_INVOKED |
| 37 | + private val onCloseHandler = atomic<Any?>(null) |
| 38 | + |
35 | 39 | // ------ internal functions for override by buffered channels ------
|
36 | 40 |
|
37 | 41 | /**
|
@@ -247,11 +251,40 @@ public abstract class AbstractSendChannel<E> : SendChannel<E> {
|
247 | 251 | }
|
248 | 252 |
|
249 | 253 | helpClose(closed)
|
| 254 | + invokeOnCloseHandler(cause) |
| 255 | + // TODO We can get rid of afterClose |
250 | 256 | onClosed(closed)
|
251 | 257 | afterClose(cause)
|
252 | 258 | return true
|
253 | 259 | }
|
254 | 260 |
|
| 261 | + private fun invokeOnCloseHandler(cause: Throwable?) { |
| 262 | + val handler = onCloseHandler.value |
| 263 | + if (handler !== null && handler !== HANDLER_INVOKED |
| 264 | + && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) { |
| 265 | + // CAS failed -> concurrent invokeOnClose() invoked handler |
| 266 | + (handler as Handler)(cause) |
| 267 | + } |
| 268 | + } |
| 269 | + |
| 270 | + override fun invokeOnClose(handler: Handler) { |
| 271 | + // Intricate dance for concurrent invokeOnClose and close calls |
| 272 | + if (!onCloseHandler.compareAndSet(null, handler)) { |
| 273 | + val value = onCloseHandler.value |
| 274 | + if (value === HANDLER_INVOKED) { |
| 275 | + throw IllegalStateException("Another handler was already registered and successfully invoked") |
| 276 | + } |
| 277 | + |
| 278 | + throw IllegalStateException("Another handler was already registered: $value") |
| 279 | + } else { |
| 280 | + val closedToken = closedForSend |
| 281 | + if (closedToken != null && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) { |
| 282 | + // CAS failed -> close() call invoked handler |
| 283 | + (handler)(closedToken.closeCause) |
| 284 | + } |
| 285 | + } |
| 286 | + } |
| 287 | + |
255 | 288 | private fun helpClose(closed: Closed<*>) {
|
256 | 289 | /*
|
257 | 290 | * It's important to traverse list from right to left to avoid races with sender.
|
@@ -983,6 +1016,9 @@ public abstract class AbstractChannel<E> : AbstractSendChannel<E>(), Channel<E>
|
983 | 1016 | /** @suppress **This is unstable API and it is subject to change.** */
|
984 | 1017 | @JvmField internal val SEND_RESUMED = Symbol("SEND_RESUMED")
|
985 | 1018 |
|
| 1019 | +internal typealias Handler = (Throwable?) -> Unit |
| 1020 | +@JvmField internal val HANDLER_INVOKED = Any() |
| 1021 | + |
986 | 1022 | /**
|
987 | 1023 | * Represents sending waiter in the queue.
|
988 | 1024 | * @suppress **This is unstable API and it is subject to change.**
|
@@ -1043,4 +1079,3 @@ private abstract class Receive<in E> : LockFreeLinkedListNode(), ReceiveOrClosed
|
1043 | 1079 | override val offerResult get() = OFFER_SUCCESS
|
1044 | 1080 | abstract fun resumeReceiveClosed(closed: Closed<*>)
|
1045 | 1081 | }
|
1046 |
| - |
|
0 commit comments