Using CompletableJob to wait for send operation running in the background

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?

2 Likes