Repository: ktorio/ktor
File: ktor-server/ktor-server-netty/jvm/src/io/ktor/server/netty/NettyApplicationResponse.kt (Kotlin)

Code before:
    public lateinit var responseMessage: Any

    @Volatile
    protected var responseMessageSent: Boolean = false

    internal var responseChannel: ByteReadChannel = ByteReadChannel.Empty

    override suspend fun respondOutgoingContent(content: OutgoingContent) {
        try {
            super.respondOutgoingContent(content)
        } catch (t: Throwable) {
            val out = responseChannel as? ByteWriteChannel
            out?.close(t)
            throw t
        } finally {
            val out = responseChannel as? ByteWriteChannel
            out?.flushAndClose()
        }
    }

    override suspend fun respondFromBytes(bytes: ByteArray) {
        // Note that it shouldn't set HttpHeaders.ContentLength even if we know it here,
        // because it should've been set by commitHeaders earlier
        val chunked = headers[HttpHeaders.TransferEncoding] == "chunked"

        if (responseMessageSent || !context.channel().isActive) return

        val message = responseMessage(chunked, bytes)
        responseChannel = when (message) {
            is LastHttpContent -> ByteReadChannel.Empty
            else -> ByteReadChannel(bytes)
        }
        responseMessage = message
        responseReady.setSuccess()
        responseMessageSent = true

        awaitProcessingResponseIfInfoOrNoContent()
    }

    override suspend fun responseChannel(): ByteWriteChannel {
        val channel = ByteChannel()
        val chunked = headers[HttpHeaders.TransferEncoding] == "chunked"
        sendResponse(chunked, content = channel)

        awaitProcessingResponseIfInfoOrNoContent()

        return channel
    }

    /**
     * Await the [NettyApplicationCall.responseWriteJob] to complete if the response status is informational or 204.

Review comment (suggestion):
Maybe a `fun canSendMessage() = !responseMessageSent && context.channel().isActive` function could be in order here.

Code after:
    public lateinit var responseMessage: Any

    @Volatile
    protected var responseMessageSent: Boolean = false

    internal var responseChannel: ByteReadChannel = ByteReadChannel.Empty

    private val canRespond: Boolean
        get() = !responseMessageSent && context.channel().isActive

    override suspend fun respondOutgoingContent(content: OutgoingContent) {
        try {
            super.respondOutgoingContent(content)
        } catch (t: Throwable) {
            val out = responseChannel as? ByteWriteChannel
            out?.close(t)
            throw t
        } finally {
            val out = responseChannel as? ByteWriteChannel
            out?.flushAndClose()
        }
    }

    override suspend fun respondFromBytes(bytes: ByteArray) {
        // Note that it shouldn't set HttpHeaders.ContentLength even if we know it here,
        // because it should've been set by commitHeaders earlier
        val chunked = headers[HttpHeaders.TransferEncoding] == "chunked"

        if (!canRespond) return

        val message = responseMessage(chunked, bytes)
        responseChannel = when (message) {
            is LastHttpContent -> ByteReadChannel.Empty
            else -> ByteReadChannel(bytes)
        }
        responseMessage = message
        responseReady.setSuccess()
        responseMessageSent = true

        awaitProcessingResponseIfInfoOrNoContent()
    }

    override suspend fun responseChannel(): ByteWriteChannel {
        val channel = ByteChannel()
        val chunked = headers[HttpHeaders.TransferEncoding] == "chunked"
        sendResponse(chunked, content = channel)

        awaitProcessingResponseIfInfoOrNoContent()

        return channel
    }