Skip to content

Commit 1317235

Browse files
committed
Fix stacktrace recovery for Channel.send
1 parent 3c4c3f3 commit 1317235

File tree

2 files changed

+27
-15
lines changed

2 files changed

+27
-15
lines changed

kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt

+15-8
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ internal open class BufferedChannel<E>(
108108
// ## The send operations ##
109109
// #########################
110110

111-
// TODO onClosed misses stacktrace recovery mechanism even though it shouldn't
112111
override suspend fun send(element: E): Unit =
113112
sendImpl( // <-- this is an inline function
114113
element = element,
@@ -123,20 +122,26 @@ internal open class BufferedChannel<E>(
123122
// According to the `send(e)` contract, we need to call
124123
// `onUndeliveredElement(..)` handler and throw an exception
125124
// if the channel is already closed.
126-
onClosed = { onClosedSend(element) },
125+
onClosed = {
126+
// Use continuation in order to recover stacktrace
127+
suspendCancellableCoroutine<Unit> {
128+
onClosedSend(element, it)
129+
}
130+
},
127131
// When `send(e)` decides to suspend, the corresponding
128132
// `onNoWaiterSuspend` function that creates a continuation
129133
// is called. The tail-call optimization is applied here.
130134
onNoWaiterSuspend = { segm, i, elem, s -> sendOnNoWaiterSuspend(segm, i, elem, s) }
131135
)
132136

133-
private fun onClosedSend(element: E) {
137+
private fun onClosedSend(element: E, continuation: Continuation<Nothing>) {
134138
onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let {
135139
// If it crashes, add send exception as suppressed for better diagnostics
136140
it.addSuppressed(sendException)
137-
throw recoverStackTrace(it)
141+
continuation.resumeWithStackTrace(it)
142+
return
138143
}
139-
throw recoverStackTrace(sendException)
144+
continuation.resumeWithStackTrace(sendException)
140145
}
141146

142147
private suspend fun sendOnNoWaiterSuspend(
@@ -188,6 +193,7 @@ internal open class BufferedChannel<E>(
188193
override fun dispose() {
189194
segment.onCancellation(index)
190195
}
196+
191197
override fun invoke(cause: Throwable?) = dispose()
192198
}
193199

@@ -199,6 +205,7 @@ internal open class BufferedChannel<E>(
199205
override fun dispose() {
200206
segment.onSenderCancellationWithOnUndeliveredElement(index, context)
201207
}
208+
202209
override fun invoke(cause: Throwable?) = dispose()
203210
}
204211

@@ -741,7 +748,7 @@ internal open class BufferedChannel<E>(
741748
invokeOnCancellation(SenderOrReceiverCancellationHandler(segment, index).asHandler)
742749
}
743750

744-
private fun onClosedReceiveOnNoWaiterSuspend(cont : CancellableContinuation<E>) {
751+
private fun onClosedReceiveOnNoWaiterSuspend(cont: CancellableContinuation<E>) {
745752
cont.resumeWithException(receiveException)
746753
}
747754

@@ -2040,7 +2047,7 @@ internal open class BufferedChannel<E>(
20402047
val globalIndex = segment.id * SEGMENT_SIZE + index
20412048
if (globalIndex < receiversCounter) return -1
20422049
// Process the cell `segment[index]`.
2043-
cell_update@while (true) {
2050+
cell_update@ while (true) {
20442051
val state = segment.getState(index)
20452052
when {
20462053
// The cell is empty.
@@ -2644,7 +2651,7 @@ internal open class BufferedChannel<E>(
26442651
append_elements@ while (true) {
26452652
process_cell@ for (i in 0 until SEGMENT_SIZE) {
26462653
val globalCellIndex = segment.id * SEGMENT_SIZE + i
2647-
if (globalCellIndex >=s && globalCellIndex >= r) break@append_elements
2654+
if (globalCellIndex >= s && globalCellIndex >= r) break@append_elements
26482655
val cellState = segment.getState(i)
26492656
val element = segment.getElement(i)
26502657
val cellStateString = when (cellState) {
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
java.util.concurrent.CancellationException: Channel was cancelled
2+
at kotlinx.coroutines.channels.BufferedChannel.cancelImpl$(BufferedChannel.kt)
3+
at kotlinx.coroutines.channels.BufferedChannel.cancel(BufferedChannel.kt)
4+
at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt)
5+
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt)
26
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
3-
at kotlinx.coroutines.internal.StackTraceRecoveryKt.recoverStackTrace(StackTraceRecovery.kt)
4-
at kotlinx.coroutines.channels.BufferedChannel.onClosedSend(BufferedChannel.kt)
5-
at kotlinx.coroutines.channels.BufferedChannel.send$suspendImpl(BufferedChannel.kt)
6-
at kotlinx.coroutines.channels.BufferedChannel.send(BufferedChannel.kt)
77
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelSend(StackTraceRecoveryChannelsTest.kt)
8-
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.access$channelSend(StackTraceRecoveryChannelsTest.kt)
9-
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$channelSend$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt)
8+
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt)
109
Caused by: java.util.concurrent.CancellationException: Channel was cancelled
1110
at kotlinx.coroutines.channels.BufferedChannel.cancelImpl$(BufferedChannel.kt)
1211
at kotlinx.coroutines.channels.BufferedChannel.cancel(BufferedChannel.kt)
1312
at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt)
1413
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt)
15-
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
14+
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
15+
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt)
16+
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt)
17+
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt)
18+
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt)
19+
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
20+
at kotlinx.coroutines.TestBase.runTest(TestBase.kt)

0 commit comments

Comments
 (0)