I have code that runs a send loop in a background coroutine. I transmit send requests through a Channel to that loop, and use an associated CompletableJob to be able to wait until that loop actually sent the data. Pseudo code:
data class SendOperation(val data: ByteArray, val job: CompletableJob = Job())
val sendChannel = Channel<SendOperation>(Channel.UNLIMITED)
// Called internally by another function
private suspend fun runSendLoop() {
while (true) {
val operation = sendChannel.receive()
try {
doSendData(operation.data)
operation.job.complete()
} catch (e: CancellationException) {
operation.job.cancel()
throw e
} catch (e: Exception) {
operation.job.completeExceptionally(e)
throw e // re-throw this since the runSendLoop() caller itself performs some additional exception handling logic
}
}
}
// The public send API
suspend fun sendData(data: ByteArray) {
val operation = SendOperation(data)
sendChannel.send(operation)
// Wait for the send operation to finish. If an exception
// was thrown while sending, re-throw that stored exception.
try {
operation.join()
} catch (e: CancellationException) {
if (e.cause != null)
throw e.cause!!
}
}
My open question is whether or not the way I handle exceptions here is okay. I essentially store the exception that happened in that background loop and re-throw it in sendData
. This assumes that completeExceptionally
will cancel the CompletableJob and the catch block in sendData
will then retrieve and re-throw the exception.
Also, I handle CancellationException
inside the background loop separately by canceling the CompletableJob in that case, assuming that a CancellationException
is thrown when I am anyway shutting things down, so this is not an abnormal situation, but a normal cancellation, so cancel
seems more fitting then than completeExceptionally
.
Are there problems with this approach? If so, which ones?