@@ -71,13 +71,13 @@ public abstract class AbstractSendChannel<E> : SendChannel<E> {
71
71
* Returns non-null closed token if it is last in the queue.
72
72
* @suppress **This is unstable API and it is subject to change.**
73
73
*/
74
- protected val closedForSend: Closed <* >? get() = queue.prevNode as ? Closed <* >
74
+ protected val closedForSend: Closed <* >? get() = ( queue.prevNode as ? Closed <* >)?. also { helpClose(it) }
75
75
76
76
/* *
77
77
* Returns non-null closed token if it is first in the queue.
78
78
* @suppress **This is unstable API and it is subject to change.**
79
79
*/
80
- protected val closedForReceive: Closed <* >? get() = queue.nextNode as ? Closed <* >
80
+ protected val closedForReceive: Closed <* >? get() = ( queue.nextNode as ? Closed <* >)?. also { helpClose(it) }
81
81
82
82
/* *
83
83
* Retrieves first sending waiter from the queue or returns closed token.
@@ -169,7 +169,9 @@ public abstract class AbstractSendChannel<E> : SendChannel<E> {
169
169
val result = offerInternal(element)
170
170
return when {
171
171
result == = OFFER_SUCCESS -> true
172
- result == = OFFER_FAILED -> false
172
+ // We should check for closed token on offer as well, otherwise offer won't be linearizable
173
+ // in the face of concurrent close()
174
+ result == = OFFER_FAILED -> throw closedForSend?.sendException ? : return false
173
175
result is Closed <* > -> throw result.sendException
174
176
else -> error(" offerInternal returned $result " )
175
177
}
@@ -231,23 +233,54 @@ public abstract class AbstractSendChannel<E> : SendChannel<E> {
231
233
232
234
public override fun close (cause : Throwable ? ): Boolean {
233
235
val closed = Closed <E >(cause)
236
+
237
+ /*
238
+ * Try to commit close by adding a close token to the end of the queue.
239
+ * Successful -> we're now responsible for closing receivers
240
+ * Not successful -> help closing pending receivers to maintain invariant
241
+ * "if (!close()) next send will throw"
242
+ */
243
+ val closeAdded = queue.addLastIfPrev(closed, { it !is Closed <* > })
244
+ if (! closeAdded) {
245
+ helpClose(queue.prevNode as Closed <* >)
246
+ return false
247
+ }
248
+
249
+ helpClose(closed)
250
+ onClosed(closed)
251
+ afterClose(cause)
252
+ return true
253
+ }
254
+
255
+ private fun helpClose (closed : Closed <* >) {
256
+ /*
257
+ * It's important to traverse list from right to left to avoid races with sender.
258
+ * Consider channel state
259
+ * head sentinel -> [receiver 1] -> [receiver 2] -> head sentinel
260
+ * T1 invokes receive()
261
+ * T2 invokes close()
262
+ * T3 invokes close() + send(value)
263
+ *
264
+ * If both will traverse list from left to right, following non-linearizable history is possible:
265
+ * [close -> false], [send -> transferred 'value' to receiver]
266
+ */
234
267
while (true ) {
235
- val receive = takeFirstReceiveOrPeekClosed()
236
- if (receive == null ) {
237
- // queue empty or has only senders -- try add last "Closed" item to the queue
238
- if (queue.addLastIfPrev(closed, { prev ->
239
- if (prev is Closed <* >) return false // already closed
240
- prev !is ReceiveOrClosed <* > // only add close if no waiting receive
241
- })) {
242
- onClosed(closed)
243
- afterClose(cause)
244
- return true
245
- }
246
- continue // retry on failure
268
+ val previous = closed.prevNode
269
+ // Channel is empty or has no receivers
270
+ if (previous is LockFreeLinkedListHead || previous !is Receive <* >) {
271
+ break
272
+ }
273
+
274
+ if (! previous.remove()) {
275
+ // failed to remove the node (due to race) -- retry finding non-removed prevNode
276
+ // NOTE: remove() DOES NOT help pending remove operation (that marked next pointer)
277
+ previous.helpRemove() // make sure remove is complete before continuing
278
+ continue
247
279
}
248
- if (receive is Closed <* >) return false // already marked as closed -- nothing to do
249
- receive as Receive <E > // type assertion
250
- receive.resumeReceiveClosed(closed)
280
+
281
+ @Suppress(" UNCHECKED_CAST" )
282
+ previous as Receive <E > // type assertion
283
+ previous.resumeReceiveClosed(closed)
251
284
}
252
285
}
253
286
0 commit comments