Repository: ktorio/ktor
File: ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (Kotlin)

Code before:
    val completed = readUTF8LineTo(result, max)
    return if (!completed) null else result.toString()
}

@OptIn(InternalAPI::class)
public suspend fun ByteReadChannel.copyTo(channel: ByteWriteChannel): Long {
    var result = 0L
    try {
        while (!isClosedForRead) {
            result += readBuffer.transferTo(channel.writeBuffer)
            channel.flush()
            awaitContent()
        }
    } catch (cause: Throwable) {
        cancel(cause)
        channel.close(cause)
        throw cause
    } finally {
        channel.flush()
    }

    return result
}

@OptIn(InternalAPI::class)
public suspend fun ByteReadChannel.readTo(sink: RawSink, limit: Long): Long {
    var remaining = limit
    try {
        while (!isClosedForRead && remaining > 0) {
            if (readBuffer.exhausted()) awaitContent()
            val byteCount = minOf(remaining, readBuffer.remaining)
            readBuffer.readTo(sink, byteCount)
            remaining -= byteCount
            sink.flush()
        }
    } catch (cause: Throwable) {
        cancel(cause)
        throw cause
    }

    return limit - remaining
}

@OptIn(InternalAPI::class)
public suspend fun ByteReadChannel.copyTo(channel: ByteWriteChannel, limit: Long): Long {
    var remaining = limit
    try {
        while (!isClosedForRead && remaining > 0) {
            if (readBuffer.exhausted()) awaitContent()
            val count = minOf(remaining, readBuffer.remaining)
            readBuffer.readTo(channel.writeBuffer, count)

Review comment (suggestion):
Could just include the default `limit = Long.MAX_VALUE` for the complete writing scenario.  I noticed the other functions of the same family use `copyTo` here, though the sink uses `readTo`.  I'm more in favour of `readTo` in terms of semantics.  Maybe we should consider renaming `copyTo` in 4.0.

Code after:
    val completed = readUTF8LineTo(result, max)
    return if (!completed) null else result.toString()
}

@OptIn(InternalAPI::class)
public suspend fun ByteReadChannel.copyTo(channel: ByteWriteChannel): Long {
    var result = 0L
    try {
        while (!isClosedForRead) {
            result += readBuffer.transferTo(channel.writeBuffer)
            channel.flush()
            awaitContent()
        }
    } catch (cause: Throwable) {
        cancel(cause)
        channel.close(cause)
        throw cause
    } finally {
        channel.flush()
    }

    return result
}

/**
 * Reads bytes from this [ByteReadChannel] and writes them to the specified [sink].
 * If an exception is thrown, the channel and sink are closed.
 *
 * @param sink the destination to write bytes to
 * @param limit the maximum number of bytes to transfer, defaults to [Long.MAX_VALUE]
 * @return the number of bytes transferred
 * @throws IOException if the channel is closed with an error
 */
@OptIn(InternalAPI::class)
public suspend fun ByteReadChannel.readTo(sink: RawSink, limit: Long = Long.MAX_VALUE): Long {
    var remaining = limit
    try {
        while (!isClosedForRead && remaining > 0) {
            if (readBuffer.exhausted()) awaitContent()
            val byteCount = minOf(remaining, readBuffer.remaining)
            readBuffer.readTo(sink, byteCount)
            remaining -= byteCount
            sink.flush()
        }
    } catch (cause: Throwable) {
        cancel(cause)
        sink.close()
        throw cause
    }

    rethrowCloseCauseIfNeeded()